diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
commit | d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch) | |
tree | 2341ef426af70ad1e289c38036737e04b0aa5007 /app/assets/javascripts/pages | |
parent | d6e514dd13db8947884cd58fe2a9c2a063400a9b (diff) |
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/pages')
36 files changed, 629 insertions, 437 deletions
diff --git a/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/usage_statistics.js b/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/usage_statistics.js index 4c312a008cb..68849857d0f 100644 --- a/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/usage_statistics.js +++ b/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/usage_statistics.js @@ -1,7 +1,7 @@ import { __ } from '~/locale'; export const HELPER_TEXT_SERVICE_PING_DISABLED = __( - 'To enable Registration Features, make sure "Enable service ping" is checked.', + 'To enable Registration Features, first enable Service Ping.', ); export const HELPER_TEXT_SERVICE_PING_ENABLED = __( diff --git a/app/assets/javascripts/pages/admin/projects/components/namespace_select.vue b/app/assets/javascripts/pages/admin/projects/components/namespace_select.vue new file mode 100644 index 00000000000..c75c031b0b1 --- /dev/null +++ b/app/assets/javascripts/pages/admin/projects/components/namespace_select.vue @@ -0,0 +1,143 @@ +<script> +import { + GlDropdown, + GlDropdownItem, + GlDropdownDivider, + GlSearchBoxByType, + GlLoadingIcon, +} from '@gitlab/ui'; +import Api from '~/api'; +import { __ } from '~/locale'; + +export default { + i18n: { + dropdownHeader: __('Namespaces'), + searchPlaceholder: __('Search for Namespace'), + anyNamespace: __('Any namespace'), + }, + components: { + GlDropdown, + GlDropdownItem, + GlDropdownDivider, + GlLoadingIcon, + GlSearchBoxByType, + }, + props: { + showAny: { + type: Boolean, + required: false, + default: false, + }, + placeholder: { + type: String, + required: false, + default: __('Namespace'), + }, + fieldName: { + type: String, + required: false, + default: null, + }, + }, + data() { + return { + namespaceOptions: [], + selectedNamespaceId: null, + selectedNamespace: null, + searchTerm: '', + isLoading: false, + }; + }, + computed: { + selectedNamespaceName() { + if (this.selectedNamespaceId === null) { + return this.placeholder; + } + return this.selectedNamespace; + }, + }, + watch: { + searchTerm() { + this.fetchNamespaces(this.searchTerm); + }, + }, + mounted() { + this.fetchNamespaces(); + }, + methods: { + fetchNamespaces(filter) { + this.isLoading = true; + this.namespaceOptions = []; + return Api.namespaces(filter, (namespaces) => { + this.namespaceOptions = namespaces; + this.isLoading = false; + }); + }, + selectNamespace(key) { + this.selectedNamespaceId = this.namespaceOptions[key].id; + this.selectedNamespace = this.getNamespaceString(this.namespaceOptions[key]); + this.$emit('setNamespace', this.selectedNamespaceId); + }, + selectAnyNamespace() { + this.selectedNamespaceId = null; + this.selectedNamespace = null; + this.$emit('setNamespace', null); + }, + getNamespaceString(namespace) { + return `${namespace.kind}: ${namespace.full_path}`; + }, + }, +}; +</script> + +<template> + <div class="gl-display-flex"> + <input + v-if="fieldName" + :name="fieldName" + :value="selectedNamespaceId" + type="hidden" + data-testid="hidden-input" + /> + <gl-dropdown + :text="selectedNamespaceName" + :header-text="$options.i18n.dropdownHeader" + toggle-class="dropdown-menu-toggle large" + data-testid="namespace-dropdown" + :right="true" + > + <template #header> + <gl-search-box-by-type + v-model.trim="searchTerm" + class="namespace-search-box" + debounce="250" + :placeholder="$options.i18n.searchPlaceholder" + /> + </template> + + <template v-if="showAny"> + <gl-dropdown-item @click="selectAnyNamespace"> + {{ $options.i18n.anyNamespace }} + </gl-dropdown-item> + <gl-dropdown-divider /> + </template> + + <gl-loading-icon v-if="isLoading" /> + + <gl-dropdown-item + v-for="(namespace, key) in namespaceOptions" + :key="namespace.id" + @click="selectNamespace(key)" + > + {{ getNamespaceString(namespace) }} + </gl-dropdown-item> + </gl-dropdown> + </div> +</template> + +<style scoped> +/* workaround position: relative imposed by .top-area .nav-controls */ +.namespace-search-box >>> input { + position: static; +} +</style> diff --git a/app/assets/javascripts/pages/admin/projects/index.js b/app/assets/javascripts/pages/admin/projects/index.js index b07ca815f13..3098d06510b 100644 --- a/app/assets/javascripts/pages/admin/projects/index.js +++ b/app/assets/javascripts/pages/admin/projects/index.js @@ -1,8 +1,38 @@ -import NamespaceSelect from '~/namespace_select'; +import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import { mergeUrlParams } from '~/lib/utils/url_utility'; import ProjectsList from '~/projects_list'; +import NamespaceSelect from './components/namespace_select.vue'; new ProjectsList(); // eslint-disable-line no-new -document - .querySelectorAll('.js-namespace-select') - .forEach((dropdown) => new NamespaceSelect({ dropdown })); +function mountNamespaceSelect() { + const el = document.querySelector('.js-namespace-select'); + if (!el) { + return false; + } + + const { showAny, fieldName, placeholder, updateLocation } = el.dataset; + + return new Vue({ + el, + render(createComponent) { + return createComponent(NamespaceSelect, { + props: { + showAny: parseBoolean(showAny), + fieldName, + placeholder, + }, + on: { + setNamespace(newNamespace) { + if (fieldName && updateLocation) { + window.location = mergeUrlParams({ [fieldName]: newNamespace }, window.location.href); + } + }, + }, + }); + }, + }); +} + +mountNamespaceSelect(); diff --git a/app/assets/javascripts/pages/admin/serverless/domains/index.js b/app/assets/javascripts/pages/admin/serverless/domains/index.js deleted file mode 100644 index 4fab7a1d9cb..00000000000 --- a/app/assets/javascripts/pages/admin/serverless/domains/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import initSettingsPanels from '~/settings_panels'; - -// Initialize expandable settings panels -initSettingsPanels(); - -const domainCard = document.querySelector('.js-domain-cert-show'); -const domainForm = document.querySelector('.js-domain-cert-inputs'); -const domainReplaceButton = document.querySelector('.js-domain-cert-replace-btn'); -const domainSubmitButton = document.querySelector('.js-serverless-domain-submit'); - -if (domainReplaceButton && domainCard && domainForm) { - domainReplaceButton.addEventListener('click', () => { - domainCard.classList.add('hidden'); - domainForm.classList.remove('hidden'); - domainSubmitButton.removeAttribute('disabled'); - }); -} diff --git a/app/assets/javascripts/pages/admin/topics/edit/index.js b/app/assets/javascripts/pages/admin/topics/edit/index.js new file mode 100644 index 00000000000..c4e05bbd092 --- /dev/null +++ b/app/assets/javascripts/pages/admin/topics/edit/index.js @@ -0,0 +1,8 @@ +import $ from 'jquery'; +import GLForm from '~/gl_form'; +import initFilePickers from '~/file_pickers'; +import ZenMode from '~/zen_mode'; + +new GLForm($('.js-project-topic-form')); // eslint-disable-line no-new +initFilePickers(); +new ZenMode(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/admin/topics/new/index.js b/app/assets/javascripts/pages/admin/topics/new/index.js new file mode 100644 index 00000000000..c4e05bbd092 --- /dev/null +++ b/app/assets/javascripts/pages/admin/topics/new/index.js @@ -0,0 +1,8 @@ +import $ from 'jquery'; +import GLForm from '~/gl_form'; +import initFilePickers from '~/file_pickers'; +import ZenMode from '~/zen_mode'; + +new GLForm($('.js-project-topic-form')); // eslint-disable-line no-new +initFilePickers(); +new ZenMode(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/groups/dependency_proxies/index.js b/app/assets/javascripts/pages/groups/dependency_proxies/index.js index 77c885d3858..862ba468296 100644 --- a/app/assets/javascripts/pages/groups/dependency_proxies/index.js +++ b/app/assets/javascripts/pages/groups/dependency_proxies/index.js @@ -1,13 +1,3 @@ -import $ from 'jquery'; -import initDependencyProxy from '~/dependency_proxy'; +import { initDependencyProxyApp } from '~/packages_and_registries/dependency_proxy/'; -initDependencyProxy(); - -const form = document.querySelector('form.edit_dependency_proxy_group_setting'); -const toggleInput = $('input.js-project-feature-toggle-input'); - -if (form && toggleInput) { - toggleInput.on('trigger-change', () => { - form.submit(); - }); -} +initDependencyProxyApp(); diff --git a/app/assets/javascripts/pages/groups/group_members/index.js b/app/assets/javascripts/pages/groups/group_members/index.js index 0137ff87979..01a371920f8 100644 --- a/app/assets/javascripts/pages/groups/group_members/index.js +++ b/app/assets/javascripts/pages/groups/group_members/index.js @@ -11,7 +11,7 @@ import { MEMBER_TYPES } from '~/members/constants'; import { groupLinkRequestFormatter } from '~/members/utils'; import UsersSelect from '~/users_select'; -const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions']; +const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions']; initMembersApp(document.querySelector('.js-group-members-list-app'), { [MEMBER_TYPES.user]: { diff --git a/app/assets/javascripts/pages/groups/packages/index/index.js b/app/assets/javascripts/pages/groups/packages/index/index.js index 1c4a10fd653..95522573b53 100644 --- a/app/assets/javascripts/pages/groups/packages/index/index.js +++ b/app/assets/javascripts/pages/groups/packages/index/index.js @@ -1,5 +1,10 @@ -import initPackageList from '~/packages/list/packages_list_app_bundle'; +(async function packageApp() { + if (window.gon.features.packageListApollo) { + const newPackageList = await import('~/packages_and_registries/package_registry/pages/list'); -if (document.getElementById('js-vue-packages-list')) { - initPackageList(); -} + newPackageList.default(); + } else { + const packageList = await import('~/packages/list/packages_list_app_bundle'); + packageList.default(); + } +})(); diff --git a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue new file mode 100644 index 00000000000..ec3cf4a8a92 --- /dev/null +++ b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue @@ -0,0 +1,176 @@ +<script> +import { GlButton, GlEmptyState, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui'; + +import { s__, __ } from '~/locale'; +import createFlash from '~/flash'; +import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; +import { joinPaths } from '~/lib/utils/url_utility'; +import { getBulkImportsHistory } from '~/rest_api'; +import ImportStatus from '~/import_entities/components/import_status.vue'; +import PaginationBar from '~/import_entities/components/pagination_bar.vue'; +import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; + +import { DEFAULT_ERROR } from '../utils/error_messages'; + +const DEFAULT_PER_PAGE = 20; +const DEFAULT_TH_CLASSES = + 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-200! gl-border-b-1! gl-p-5!'; + +const tableCell = (config) => ({ + thClass: `${DEFAULT_TH_CLASSES}`, + tdClass: (value, key, item) => { + return { + // eslint-disable-next-line no-underscore-dangle + 'gl-border-b-0!': item._showDetails, + }; + }, + ...config, +}); + +export default { + components: { + GlButton, + GlEmptyState, + GlLink, + GlLoadingIcon, + GlTable, + PaginationBar, + ImportStatus, + TimeAgo, + }, + + data() { + return { + loading: true, + historyItems: [], + paginationConfig: { + page: 1, + perPage: DEFAULT_PER_PAGE, + }, + pageInfo: {}, + }; + }, + + fields: [ + tableCell({ + key: 'source_full_path', + label: s__('BulkImport|Source group'), + thClass: `${DEFAULT_TH_CLASSES} gl-w-30p`, + }), + tableCell({ + key: 'destination_name', + label: s__('BulkImport|New group'), + thClass: `${DEFAULT_TH_CLASSES} gl-w-40p`, + }), + tableCell({ + key: 'created_at', + label: __('Date'), + }), + tableCell({ + key: 'status', + label: __('Status'), + tdAttr: { 'data-qa-selector': 'import_status_indicator' }, + }), + ], + + computed: { + hasHistoryItems() { + return this.historyItems.length > 0; + }, + }, + + watch: { + paginationConfig: { + handler() { + this.loadHistoryItems(); + }, + deep: true, + immediate: true, + }, + }, + + methods: { + async loadHistoryItems() { + try { + this.loading = true; + const { data: historyItems, headers } = await getBulkImportsHistory({ + page: this.paginationConfig.page, + per_page: this.paginationConfig.perPage, + }); + this.pageInfo = parseIntPagination(normalizeHeaders(headers)); + this.historyItems = historyItems; + } catch (e) { + createFlash({ message: DEFAULT_ERROR, captureError: true, error: e }); + } finally { + this.loading = false; + } + }, + + getDestinationUrl({ destination_name: name, destination_namespace: namespace }) { + return [namespace, name].filter(Boolean).join('/'); + }, + + getFullDestinationUrl(params) { + return joinPaths(gon.relative_url_root || '', this.getDestinationUrl(params)); + }, + }, + + gitlabLogo: window.gon.gitlab_logo, +}; +</script> + +<template> + <div> + <div + class="gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-display-flex gl-align-items-center" + > + <h1 class="gl-my-0 gl-py-4 gl-font-size-h1"> + <img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6 gl-mb-2 gl-display-inline gl-mr-2" /> + {{ s__('BulkImport|Group import history') }} + </h1> + </div> + <gl-loading-icon v-if="loading" size="md" class="gl-mt-5" /> + <gl-empty-state + v-else-if="!hasHistoryItems" + :title="s__('BulkImport|No history is available')" + :description="s__('BulkImport|Your imported groups will appear here.')" + /> + <template v-else> + <gl-table + :fields="$options.fields" + :items="historyItems" + data-qa-selector="import_history_table" + class="gl-w-full" + > + <template #cell(destination_name)="{ item }"> + <gl-link :href="getFullDestinationUrl(item)" target="_blank"> + {{ getDestinationUrl(item) }} + </gl-link> + </template> + <template #cell(created_at)="{ value }"> + <time-ago :time="value" /> + </template> + <template #cell(status)="{ value, item, toggleDetails, detailsShowing }"> + <import-status :status="value" class="gl-display-inline-block gl-w-13" /> + <gl-button + v-if="item.failures.length" + class="gl-ml-3" + :selected="detailsShowing" + @click="toggleDetails" + >{{ __('Details') }}</gl-button + > + </template> + <template #row-details="{ item }"> + <pre>{{ item.failures }}</pre> + </template> + </gl-table> + <pagination-bar + :page-info="pageInfo" + :items-count="historyItems.length" + class="gl-m-0 gl-mt-3" + @set-page="paginationConfig.page = $event" + @set-page-size="paginationConfig.perPage = $event" + /> + </template> + </div> +</template> diff --git a/app/assets/javascripts/pages/import/bulk_imports/history/index.js b/app/assets/javascripts/pages/import/bulk_imports/history/index.js new file mode 100644 index 00000000000..5a67aa99baa --- /dev/null +++ b/app/assets/javascripts/pages/import/bulk_imports/history/index.js @@ -0,0 +1,15 @@ +import Vue from 'vue'; +import BulkImportHistoryApp from './components/bulk_imports_history_app.vue'; + +function mountImportHistoryApp(mountElement) { + if (!mountElement) return undefined; + + return new Vue({ + el: mountElement, + render(createElement) { + return createElement(BulkImportHistoryApp); + }, + }); +} + +mountImportHistoryApp(document.querySelector('#import-history-mount-element')); diff --git a/app/assets/javascripts/pages/import/bulk_imports/history/utils/error_messages.js b/app/assets/javascripts/pages/import/bulk_imports/history/utils/error_messages.js new file mode 100644 index 00000000000..24669e22ade --- /dev/null +++ b/app/assets/javascripts/pages/import/bulk_imports/history/utils/error_messages.js @@ -0,0 +1,3 @@ +import { __ } from '~/locale'; + +export const DEFAULT_ERROR = __('Something went wrong on our end.'); diff --git a/app/assets/javascripts/pages/profiles/index.js b/app/assets/javascripts/pages/profiles/index.js index 80bc32dd43f..6afb3636998 100644 --- a/app/assets/javascripts/pages/profiles/index.js +++ b/app/assets/javascripts/pages/profiles/index.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import '~/profile/gl_crop'; import Profile from '~/profile/profile'; import initSearchSettings from '~/search_settings'; +import initPasswordPrompt from './password_prompt'; // eslint-disable-next-line func-names $(document).on('input.ssh_key', '#key_key', function () { @@ -19,3 +20,4 @@ $(document).on('input.ssh_key', '#key_key', function () { new Profile(); // eslint-disable-line no-new initSearchSettings(); +initPasswordPrompt(); diff --git a/app/assets/javascripts/pages/profiles/password_prompt/constants.js b/app/assets/javascripts/pages/profiles/password_prompt/constants.js new file mode 100644 index 00000000000..99b8442c928 --- /dev/null +++ b/app/assets/javascripts/pages/profiles/password_prompt/constants.js @@ -0,0 +1,9 @@ +import { __, s__ } from '~/locale'; + +export const I18N_PASSWORD_PROMPT_TITLE = s__('PasswordPrompt|Confirm password to continue'); +export const I18N_PASSWORD_PROMPT_FORM_LABEL = s__( + 'PasswordPrompt|Please enter your password to confirm', +); +export const I18N_PASSWORD_PROMPT_ERROR_MESSAGE = s__('PasswordPrompt|Password is required'); +export const I18N_PASSWORD_PROMPT_CONFIRM_BUTTON = s__('PasswordPrompt|Confirm password'); +export const I18N_PASSWORD_PROMPT_CANCEL_BUTTON = __('Cancel'); diff --git a/app/assets/javascripts/pages/profiles/password_prompt/index.js b/app/assets/javascripts/pages/profiles/password_prompt/index.js new file mode 100644 index 00000000000..20645112893 --- /dev/null +++ b/app/assets/javascripts/pages/profiles/password_prompt/index.js @@ -0,0 +1,58 @@ +import Vue from 'vue'; +import Translate from '~/vue_shared/translate'; +import PasswordPromptModal from './password_prompt_modal.vue'; + +Vue.use(Translate); + +const emailFieldSelector = '#user_email'; +const editFormSelector = '.js-password-prompt-form'; +const passwordPromptFieldSelector = '.js-password-prompt-field'; +const passwordPromptBtnSelector = '.js-password-prompt-btn'; + +const passwordPromptModalId = 'password-prompt-modal'; + +const getEmailValue = () => document.querySelector(emailFieldSelector).value.trim(); +const passwordPromptButton = document.querySelector(passwordPromptBtnSelector); +const field = document.querySelector(passwordPromptFieldSelector); +const form = document.querySelector(editFormSelector); + +const handleConfirmPassword = (pw) => { + // update the validation_password field + field.value = pw; + // submit the form + form.submit(); +}; + +export default () => { + const passwordPromptModalEl = document.getElementById(passwordPromptModalId); + + if (passwordPromptModalEl && field) { + return new Vue({ + el: passwordPromptModalEl, + data() { + return { + initialEmail: '', + }; + }, + mounted() { + this.initialEmail = getEmailValue(); + passwordPromptButton.addEventListener('click', this.handleSettingsUpdate); + }, + methods: { + handleSettingsUpdate(ev) { + const email = getEmailValue(); + if (email !== this.initialEmail) { + ev.preventDefault(); + this.$root.$emit('bv::show::modal', passwordPromptModalId, passwordPromptBtnSelector); + } + }, + }, + render(createElement) { + return createElement(PasswordPromptModal, { + props: { handleConfirmPassword }, + }); + }, + }); + } + return null; +}; diff --git a/app/assets/javascripts/pages/profiles/password_prompt/password_prompt_modal.vue b/app/assets/javascripts/pages/profiles/password_prompt/password_prompt_modal.vue new file mode 100644 index 00000000000..44728ea9cdf --- /dev/null +++ b/app/assets/javascripts/pages/profiles/password_prompt/password_prompt_modal.vue @@ -0,0 +1,82 @@ +<script> +import { GlModal, GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui'; +import { + I18N_PASSWORD_PROMPT_TITLE, + I18N_PASSWORD_PROMPT_FORM_LABEL, + I18N_PASSWORD_PROMPT_ERROR_MESSAGE, + I18N_PASSWORD_PROMPT_CANCEL_BUTTON, + I18N_PASSWORD_PROMPT_CONFIRM_BUTTON, +} from './constants'; + +export default { + components: { + GlModal, + GlForm, + GlFormGroup, + GlFormInput, + }, + props: { + handleConfirmPassword: { + type: Function, + required: true, + }, + }, + data() { + return { + passwordCheck: '', + }; + }, + computed: { + isValid() { + return Boolean(this.passwordCheck.length); + }, + primaryProps() { + return { + text: I18N_PASSWORD_PROMPT_CONFIRM_BUTTON, + attributes: [{ variant: 'danger' }, { category: 'primary' }, { disabled: !this.isValid }], + }; + }, + }, + methods: { + onConfirmPassword() { + this.handleConfirmPassword(this.passwordCheck); + }, + }, + cancelProps: { + text: I18N_PASSWORD_PROMPT_CANCEL_BUTTON, + }, + i18n: { + title: I18N_PASSWORD_PROMPT_TITLE, + formLabel: I18N_PASSWORD_PROMPT_FORM_LABEL, + errorMessage: I18N_PASSWORD_PROMPT_ERROR_MESSAGE, + }, +}; +</script> + +<template> + <gl-modal + data-testid="password-prompt-modal" + modal-id="password-prompt-modal" + :title="$options.i18n.title" + :action-primary="primaryProps" + :action-cancel="$options.cancelProps" + @primary="onConfirmPassword" + > + <gl-form @submit.prevent="onConfirmPassword"> + <gl-form-group + :label="$options.i18n.formLabel" + label-for="password-prompt-confirmation" + :invalid-feedback="$options.i18n.errorMessage" + :state="isValid" + > + <gl-form-input + id="password-prompt-confirmation" + v-model="passwordCheck" + name="password-confirmation" + type="password" + data-testid="password-prompt-field" + /> + </gl-form-group> + </gl-form> + </gl-modal> +</template> diff --git a/app/assets/javascripts/pages/projects/cluster_agents/show/index.js b/app/assets/javascripts/pages/projects/cluster_agents/show/index.js new file mode 100644 index 00000000000..4ed3e2f7bea --- /dev/null +++ b/app/assets/javascripts/pages/projects/cluster_agents/show/index.js @@ -0,0 +1,3 @@ +import loadClusterAgentVues from '~/clusters/agents'; + +loadClusterAgentVues(); diff --git a/app/assets/javascripts/pages/projects/clusters/index/index.js b/app/assets/javascripts/pages/projects/clusters/index/index.js index 2b5451bd18b..a1ba920b322 100644 --- a/app/assets/javascripts/pages/projects/clusters/index/index.js +++ b/app/assets/javascripts/pages/projects/clusters/index/index.js @@ -1,4 +1,4 @@ -import initClustersListApp from 'ee_else_ce/clusters_list'; +import initClustersListApp from '~/clusters_list'; import PersistentUserCallout from '~/persistent_user_callout'; const callout = document.querySelector('.gcp-signup-offer'); diff --git a/app/assets/javascripts/pages/projects/new/components/app.vue b/app/assets/javascripts/pages/projects/new/components/app.vue deleted file mode 100644 index 6e9efc50be8..00000000000 --- a/app/assets/javascripts/pages/projects/new/components/app.vue +++ /dev/null @@ -1,124 +0,0 @@ -<script> -import createFromTemplateIllustration from '@gitlab/svgs/dist/illustrations/project-create-from-template-sm.svg'; -import blankProjectIllustration from '@gitlab/svgs/dist/illustrations/project-create-new-sm.svg'; -import importProjectIllustration from '@gitlab/svgs/dist/illustrations/project-import-sm.svg'; -import ciCdProjectIllustration from '@gitlab/svgs/dist/illustrations/project-run-CICD-pipelines-sm.svg'; -import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; -import { s__ } from '~/locale'; -import NewNamespacePage from '~/vue_shared/new_namespace/new_namespace_page.vue'; -import NewProjectPushTipPopover from './new_project_push_tip_popover.vue'; - -const CI_CD_PANEL = 'cicd_for_external_repo'; -const PANELS = [ - { - key: 'blank', - name: 'blank_project', - selector: '#blank-project-pane', - title: s__('ProjectsNew|Create blank project'), - description: s__( - 'ProjectsNew|Create a blank project to house your files, plan your work, and collaborate on code, among other things.', - ), - illustration: blankProjectIllustration, - }, - { - key: 'template', - name: 'create_from_template', - selector: '#create-from-template-pane', - title: s__('ProjectsNew|Create from template'), - description: s__( - 'ProjectsNew|Create a project pre-populated with the necessary files to get you started quickly.', - ), - illustration: createFromTemplateIllustration, - }, - { - key: 'import', - name: 'import_project', - selector: '#import-project-pane', - title: s__('ProjectsNew|Import project'), - description: s__( - 'ProjectsNew|Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab.', - ), - illustration: importProjectIllustration, - }, - { - key: 'ci', - name: CI_CD_PANEL, - selector: '#ci-cd-project-pane', - title: s__('ProjectsNew|Run CI/CD for external repository'), - description: s__('ProjectsNew|Connect your external repository to GitLab CI/CD.'), - illustration: ciCdProjectIllustration, - }, -]; - -export default { - components: { - NewNamespacePage, - NewProjectPushTipPopover, - }, - directives: { - SafeHtml, - }, - props: { - hasErrors: { - type: Boolean, - required: false, - default: false, - }, - isCiCdAvailable: { - type: Boolean, - required: false, - default: false, - }, - newProjectGuidelines: { - type: String, - required: false, - default: '', - }, - }, - - computed: { - availablePanels() { - return this.isCiCdAvailable ? PANELS : PANELS.filter((p) => p.name !== CI_CD_PANEL); - }, - }, - - methods: { - resetProjectErrors() { - const errorsContainer = document.querySelector('.project-edit-errors'); - if (errorsContainer) { - errorsContainer.innerHTML = ''; - } - }, - }, -}; -</script> - -<template> - <new-namespace-page - :initial-breadcrumb="s__('New project')" - :panels="availablePanels" - :jump-to-last-persisted-panel="hasErrors" - :title="s__('ProjectsNew|Create new project')" - persistence-key="new_project_last_active_tab" - @panel-change="resetProjectErrors" - > - <template #extra-description> - <div - v-if="newProjectGuidelines" - id="new-project-guideline" - v-safe-html="newProjectGuidelines" - ></div> - </template> - <template #welcome-footer> - <div class="gl-pt-5 gl-text-center"> - <p> - {{ __('You can also create a project from the command line.') }} - <a ref="clipTip" href="#" @click.prevent> - {{ __('Show command') }} - </a> - <new-project-push-tip-popover :target="() => $refs.clipTip" /> - </p> - </div> - </template> - </new-namespace-page> -</template> diff --git a/app/assets/javascripts/pages/projects/new/components/new_project_push_tip_popover.vue b/app/assets/javascripts/pages/projects/new/components/new_project_push_tip_popover.vue deleted file mode 100644 index e42d9154866..00000000000 --- a/app/assets/javascripts/pages/projects/new/components/new_project_push_tip_popover.vue +++ /dev/null @@ -1,66 +0,0 @@ -<script> -import { GlPopover, GlFormInputGroup } from '@gitlab/ui'; -import { __ } from '~/locale'; -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; - -export default { - components: { - GlPopover, - GlFormInputGroup, - ClipboardButton, - }, - inject: ['pushToCreateProjectCommand', 'workingWithProjectsHelpPath'], - props: { - target: { - type: [Function, HTMLElement], - required: true, - }, - }, - i18n: { - clipboardButtonTitle: __('Copy command'), - commandInputAriaLabel: __('Push project from command line'), - helpLinkText: __('What does this command do?'), - labelText: __('Private projects can be created in your personal namespace with:'), - popoverTitle: __('Push to create a project'), - }, -}; -</script> -<template> - <gl-popover - :target="target" - :title="$options.i18n.popoverTitle" - triggers="click blur" - placement="top" - > - <p> - <label for="push-to-create-tip" class="gl-font-weight-normal"> - {{ $options.i18n.labelText }} - </label> - </p> - <p> - <gl-form-input-group - id="push-to-create-tip" - :value="pushToCreateProjectCommand" - readonly - select-on-click - :aria-label="$options.i18n.commandInputAriaLabel" - > - <template #append> - <clipboard-button - :text="pushToCreateProjectCommand" - :title="$options.i18n.clipboardButtonTitle" - tooltip-placement="right" - /> - </template> - </gl-form-input-group> - </p> - <p> - <a - :href="`${workingWithProjectsHelpPath}#push-to-create-a-new-project`" - class="gl-font-sm" - target="_blank" - >{{ $options.i18n.helpLinkText }}</a - > - </p> - </gl-popover> -</template> diff --git a/app/assets/javascripts/pages/projects/new/components/new_project_url_select.vue b/app/assets/javascripts/pages/projects/new/components/new_project_url_select.vue deleted file mode 100644 index ba8858c985a..00000000000 --- a/app/assets/javascripts/pages/projects/new/components/new_project_url_select.vue +++ /dev/null @@ -1,98 +0,0 @@ -<script> -import { - GlButton, - GlButtonGroup, - GlDropdown, - GlDropdownItem, - GlDropdownSectionHeader, - GlLoadingIcon, - GlSearchBoxByType, -} from '@gitlab/ui'; -import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import Tracking from '~/tracking'; -import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants'; -import searchNamespacesWhereUserCanCreateProjectsQuery from '../queries/search_namespaces_where_user_can_create_projects.query.graphql'; - -export default { - components: { - GlButton, - GlButtonGroup, - GlDropdown, - GlDropdownItem, - GlDropdownSectionHeader, - GlLoadingIcon, - GlSearchBoxByType, - }, - mixins: [Tracking.mixin()], - apollo: { - currentUser: { - query: searchNamespacesWhereUserCanCreateProjectsQuery, - variables() { - return { - search: this.search, - }; - }, - skip() { - return this.search.length > 0 && this.search.length < MINIMUM_SEARCH_LENGTH; - }, - debounce: DEBOUNCE_DELAY, - }, - }, - inject: ['namespaceFullPath', 'namespaceId', 'rootUrl', 'trackLabel'], - data() { - return { - currentUser: {}, - search: '', - selectedNamespace: { - id: this.namespaceId, - fullPath: this.namespaceFullPath, - }, - }; - }, - computed: { - userGroups() { - return this.currentUser.groups?.nodes || []; - }, - userNamespace() { - return this.currentUser.namespace || {}; - }, - }, - methods: { - handleClick({ id, fullPath }) { - this.selectedNamespace = { - id: getIdFromGraphQLId(id), - fullPath, - }; - }, - }, -}; -</script> - -<template> - <gl-button-group class="gl-w-full"> - <gl-button label>{{ rootUrl }}</gl-button> - <gl-dropdown - class="gl-w-full" - :text="selectedNamespace.fullPath" - toggle-class="gl-rounded-top-right-base! gl-rounded-bottom-right-base!" - data-qa-selector="select_namespace_dropdown" - @show="track('activate_form_input', { label: trackLabel, property: 'project_path' })" - > - <gl-search-box-by-type v-model.trim="search" /> - <gl-loading-icon v-if="$apollo.queries.currentUser.loading" /> - <template v-else> - <gl-dropdown-section-header>{{ __('Groups') }}</gl-dropdown-section-header> - <gl-dropdown-item v-for="group of userGroups" :key="group.id" @click="handleClick(group)"> - {{ group.fullPath }} - </gl-dropdown-item> - <gl-dropdown-section-header>{{ __('Users') }}</gl-dropdown-section-header> - <gl-dropdown-item @click="handleClick(userNamespace)"> - {{ userNamespace.fullPath }} - </gl-dropdown-item> - </template> - </gl-dropdown> - - <input type="hidden" name="project[namespace_id]" :value="selectedNamespace.id" /> - </gl-button-group> -</template> diff --git a/app/assets/javascripts/pages/projects/new/index.js b/app/assets/javascripts/pages/projects/new/index.js index ed816e3be95..d89b4d0e0a3 100644 --- a/app/assets/javascripts/pages/projects/new/index.js +++ b/app/assets/javascripts/pages/projects/new/index.js @@ -1,66 +1,6 @@ -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import createDefaultClient from '~/lib/graphql'; -import { parseBoolean } from '~/lib/utils/common_utils'; -import initProjectVisibilitySelector from '../../../project_visibility'; -import initProjectNew from '../../../projects/project_new'; -import NewProjectCreationApp from './components/app.vue'; -import NewProjectUrlSelect from './components/new_project_url_select.vue'; - -function initNewProjectCreation() { - const el = document.querySelector('.js-new-project-creation'); - - const { - pushToCreateProjectCommand, - workingWithProjectsHelpPath, - newProjectGuidelines, - hasErrors, - isCiCdAvailable, - } = el.dataset; - - const props = { - hasErrors: parseBoolean(hasErrors), - isCiCdAvailable: parseBoolean(isCiCdAvailable), - newProjectGuidelines, - }; - - const provide = { - workingWithProjectsHelpPath, - pushToCreateProjectCommand, - }; - - return new Vue({ - el, - provide, - render(h) { - return h(NewProjectCreationApp, { props }); - }, - }); -} - -function initNewProjectUrlSelect() { - const el = document.querySelector('.js-vue-new-project-url-select'); - - if (!el) { - return undefined; - } - - Vue.use(VueApollo); - - return new Vue({ - el, - apolloProvider: new VueApollo({ - defaultClient: createDefaultClient({}, { assumeImmutableResults: true }), - }), - provide: { - namespaceFullPath: el.dataset.namespaceFullPath, - namespaceId: el.dataset.namespaceId, - rootUrl: el.dataset.rootUrl, - trackLabel: el.dataset.trackLabel, - }, - render: (createElement) => createElement(NewProjectUrlSelect), - }); -} +import { initNewProjectCreation, initNewProjectUrlSelect } from '~/projects/new'; +import initProjectVisibilitySelector from '~/project_visibility'; +import initProjectNew from '~/projects/project_new'; initProjectVisibilitySelector(); initProjectNew.bindEvents(); diff --git a/app/assets/javascripts/pages/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql b/app/assets/javascripts/pages/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql deleted file mode 100644 index e16fe5dde49..00000000000 --- a/app/assets/javascripts/pages/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql +++ /dev/null @@ -1,14 +0,0 @@ -query searchNamespacesWhereUserCanCreateProjects($search: String) { - currentUser { - groups(permissionScope: CREATE_PROJECTS, search: $search) { - nodes { - id - fullPath - } - } - namespace { - id - fullPath - } - } -} diff --git a/app/assets/javascripts/pages/projects/packages/packages/index/index.js b/app/assets/javascripts/pages/projects/packages/packages/index/index.js index c94782fdf1b..95522573b53 100644 --- a/app/assets/javascripts/pages/projects/packages/packages/index/index.js +++ b/app/assets/javascripts/pages/projects/packages/packages/index/index.js @@ -1,3 +1,10 @@ -import initPackageList from '~/packages/list/packages_list_app_bundle'; +(async function packageApp() { + if (window.gon.features.packageListApollo) { + const newPackageList = await import('~/packages_and_registries/package_registry/pages/list'); -initPackageList(); + newPackageList.default(); + } else { + const packageList = await import('~/packages/list/packages_list_app_bundle'); + packageList.default(); + } +})(); diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js index 16c4a6191b2..e92b9b30fa4 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js @@ -27,19 +27,22 @@ export const findTimezoneByIdentifier = (tzList = [], identifier = null) => { }; export default class TimezoneDropdown { - constructor({ $dropdownEl, $inputEl, onSelectTimezone, displayFormat } = defaults) { + constructor({ + $dropdownEl, + $inputEl, + onSelectTimezone, + displayFormat, + allowEmpty = false, + } = defaults) { this.$dropdown = $dropdownEl; this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text'); this.$input = $inputEl; - this.timezoneData = this.$dropdown.data('data'); + this.timezoneData = this.$dropdown.data('data') || []; this.onSelectTimezone = onSelectTimezone; this.displayFormat = displayFormat || defaults.displayFormat; + this.allowEmpty = allowEmpty; - this.initialTimezone = - findTimezoneByIdentifier(this.timezoneData, this.$input.val()) || defaultTimezone; - - this.initDefaultTimezone(); this.initDropdown(); } @@ -52,24 +55,25 @@ export default class TimezoneDropdown { search: { fields: ['name'], }, - clicked: (cfg) => this.updateInputValue(cfg), + clicked: (cfg) => this.handleDropdownChange(cfg), text: (item) => formatTimezone(item), }); - this.setDropdownToggle(this.displayFormat(this.initialTimezone)); - } + const initialTimezone = findTimezoneByIdentifier(this.timezoneData, this.$input.val()); - initDefaultTimezone() { - if (!this.$input.val()) { - this.$input.val(defaultTimezone.name); + if (initialTimezone !== null) { + this.setDropdownValue(initialTimezone); + } else if (!this.allowEmpty) { + this.setDropdownValue(defaultTimezone); } } - setDropdownToggle(dropdownText) { - this.$dropdownToggle.text(dropdownText); + setDropdownValue(timezone) { + this.$dropdownToggle.text(this.displayFormat(timezone)); + this.$input.val(timezone.name); } - updateInputValue({ selectedObj, e }) { + handleDropdownChange({ selectedObj, e }) { e.preventDefault(); this.$input.val(selectedObj.identifier); if (this.onSelectTimezone) { diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js index 0b662c945c6..947bbdacf2c 100644 --- a/app/assets/javascripts/pages/projects/project_members/index.js +++ b/app/assets/javascripts/pages/projects/project_members/index.js @@ -26,7 +26,7 @@ initInviteMembersForm(); new UsersSelect(); // eslint-disable-line no-new -const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions']; +const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions']; initMembersApp(document.querySelector('.js-project-members-list-app'), { [MEMBER_TYPES.user]: { tableFields: SHARED_FIELDS.concat(['source', 'granted']), diff --git a/app/assets/javascripts/pages/projects/wikis/diff/index.js b/app/assets/javascripts/pages/projects/wikis/diff/index.js new file mode 100644 index 00000000000..73440db761f --- /dev/null +++ b/app/assets/javascripts/pages/projects/wikis/diff/index.js @@ -0,0 +1,3 @@ +import { initDiffStatsDropdown } from '~/init_diff_stats_dropdown'; + +initDiffStatsDropdown(); diff --git a/app/assets/javascripts/pages/projects/wikis/edit/index.js b/app/assets/javascripts/pages/projects/wikis/edit/index.js new file mode 100644 index 00000000000..b2288c2655c --- /dev/null +++ b/app/assets/javascripts/pages/projects/wikis/edit/index.js @@ -0,0 +1,3 @@ +import { mountApplications } from '~/pages/shared/wikis/edit'; + +mountApplications(); diff --git a/app/assets/javascripts/pages/projects/wikis/git_access/index.js b/app/assets/javascripts/pages/projects/wikis/git_access/index.js new file mode 100644 index 00000000000..b1f3006bc1a --- /dev/null +++ b/app/assets/javascripts/pages/projects/wikis/git_access/index.js @@ -0,0 +1,3 @@ +import initClonePanel from '~/clone_panel'; + +initClonePanel(); diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js index 2c1f9e634ab..83fcd348ddf 100644 --- a/app/assets/javascripts/pages/projects/wikis/index.js +++ b/app/assets/javascripts/pages/projects/wikis/index.js @@ -1,5 +1,3 @@ -import { initDiffStatsDropdown } from '~/init_diff_stats_dropdown'; -import initWikis from '~/pages/shared/wikis'; +import Wikis from '~/pages/shared/wikis/wikis'; -initWikis(); -initDiffStatsDropdown(); +export default new Wikis(); diff --git a/app/assets/javascripts/pages/projects/wikis/show/index.js b/app/assets/javascripts/pages/projects/wikis/show/index.js new file mode 100644 index 00000000000..c08a10122b6 --- /dev/null +++ b/app/assets/javascripts/pages/projects/wikis/show/index.js @@ -0,0 +1,3 @@ +import { mountApplications as mountEditApplications } from '~/pages/shared/wikis/async_edit'; + +mountEditApplications(); diff --git a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js index 8d2d5d41f6a..ee48543f0d2 100644 --- a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js +++ b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js @@ -20,7 +20,7 @@ export default class OAuthRememberMe { toggleRememberMe(event) { const rememberMe = $(event.target).is(':checked'); - $('.oauth-login', this.container).each((i, element) => { + $('.js-oauth-login', this.container).each((i, element) => { const $form = $(element).parent('form'); const href = $form.attr('action'); diff --git a/app/assets/javascripts/pages/shared/mount_runner_instructions.js b/app/assets/javascripts/pages/shared/mount_runner_instructions.js index e83c73edfde..1cb7259be64 100644 --- a/app/assets/javascripts/pages/shared/mount_runner_instructions.js +++ b/app/assets/javascripts/pages/shared/mount_runner_instructions.js @@ -9,7 +9,12 @@ export function initInstallRunner(componentId = 'js-install-runner') { const installRunnerEl = document.getElementById(componentId); if (installRunnerEl) { - const defaultClient = createDefaultClient(); + const defaultClient = createDefaultClient( + {}, + { + assumeImmutableResults: true, + }, + ); const apolloProvider = new VueApollo({ defaultClient, diff --git a/app/assets/javascripts/pages/shared/wikis/async_edit.js b/app/assets/javascripts/pages/shared/wikis/async_edit.js new file mode 100644 index 00000000000..4536a076568 --- /dev/null +++ b/app/assets/javascripts/pages/shared/wikis/async_edit.js @@ -0,0 +1,11 @@ +export const mountApplications = async () => { + const el = document.querySelector('.js-wiki-edit-page'); + + if (el) { + const { mountApplications: mountEditApplications } = await import( + /* webpackChunkName: 'wiki_edit' */ './edit' + ); + + mountEditApplications(); + } +}; diff --git a/app/assets/javascripts/pages/shared/wikis/index.js b/app/assets/javascripts/pages/shared/wikis/edit.js index 42aefe81325..beeabfde1a6 100644 --- a/app/assets/javascripts/pages/shared/wikis/index.js +++ b/app/assets/javascripts/pages/shared/wikis/edit.js @@ -1,6 +1,5 @@ import $ from 'jquery'; import Vue from 'vue'; -import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import csrf from '~/lib/utils/csrf'; import Translate from '~/vue_shared/translate'; @@ -9,14 +8,8 @@ import ZenMode from '../../../zen_mode'; import deleteWikiModal from './components/delete_wiki_modal.vue'; import wikiAlert from './components/wiki_alert.vue'; import wikiForm from './components/wiki_form.vue'; -import Wikis from './wikis'; const createModalVueApp = () => { - new Wikis(); // eslint-disable-line no-new - new ShortcutsWiki(); // eslint-disable-line no-new - new ZenMode(); // eslint-disable-line no-new - new GLForm($('.wiki-form')); // eslint-disable-line no-new - const deleteWikiModalWrapperEl = document.getElementById('delete-wiki-modal-wrapper'); if (deleteWikiModalWrapperEl) { @@ -85,7 +78,10 @@ const createWikiFormApp = () => { } }; -export default () => { +export const mountApplications = () => { + new ZenMode(); // eslint-disable-line no-new + new GLForm($('.wiki-form')); // eslint-disable-line no-new + createModalVueApp(); createAlertVueApp(); createWikiFormApp(); diff --git a/app/assets/javascripts/pages/shared/wikis/wikis.js b/app/assets/javascripts/pages/shared/wikis/wikis.js index 7d0b0c90c8d..8d0105bc681 100644 --- a/app/assets/javascripts/pages/shared/wikis/wikis.js +++ b/app/assets/javascripts/pages/shared/wikis/wikis.js @@ -1,6 +1,7 @@ import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import Tracking from '~/tracking'; import showToast from '~/vue_shared/plugins/global_toast'; +import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki'; const TRACKING_EVENT_NAME = 'view_wiki_page'; const TRACKING_CONTEXT_SCHEMA = 'iglu:com.gitlab/wiki_page_context/jsonschema/1-0-1'; @@ -20,6 +21,7 @@ export default class Wikis { Wikis.trackPageView(); Wikis.showToasts(); + Wikis.initShortcuts(); } handleToggleSidebar(e) { @@ -64,4 +66,8 @@ export default class Wikis { const toasts = document.querySelectorAll('.js-toast-message'); toasts.forEach((toast) => showToast(toast.dataset.message)); } + + static initShortcuts() { + new ShortcutsWiki(); // eslint-disable-line no-new + } } |