diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 10:00:54 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 10:00:54 +0000 |
commit | 3cccd102ba543e02725d247893729e5c73b38295 (patch) | |
tree | f36a04ec38517f5deaaacb5acc7d949688d1e187 /app/assets/javascripts/pages | |
parent | 205943281328046ef7b4528031b90fbda70c75ac (diff) | |
download | gitlab-ce-3cccd102ba543e02725d247893729e5c73b38295.tar.gz |
Add latest changes from gitlab-org/gitlab@14-10-stable-eev14.10.0-rc42
Diffstat (limited to 'app/assets/javascripts/pages')
49 files changed, 702 insertions, 383 deletions
diff --git a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js index e78b3f9ec95..29e92a8abad 100644 --- a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js +++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import { parseBoolean } from '~/lib/utils/common_utils'; -import { truncate } from '../../../lib/utils/text_utility'; +import { truncate } from '~/lib/utils/text_utility'; const MAX_MESSAGE_LENGTH = 500; const MESSAGE_CELL_SELECTOR = '.abuse-reports .message'; diff --git a/app/assets/javascripts/pages/admin/admin.js b/app/assets/javascripts/pages/admin/admin.js index 6b7bfbf217d..e6c1d6d147b 100644 --- a/app/assets/javascripts/pages/admin/admin.js +++ b/app/assets/javascripts/pages/admin/admin.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import { refreshCurrentPage } from '../../lib/utils/url_utility'; +import { refreshCurrentPage } from '~/lib/utils/url_utility'; export default function adminInit() { $('input#user_force_random_password').on('change', function randomPasswordClick() { diff --git a/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js b/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js index 67eee2c3209..7c81cf80dc6 100644 --- a/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js +++ b/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js @@ -1,6 +1,6 @@ import createFlash from '~/flash'; -import axios from '../../../lib/utils/axios_utils'; -import { __ } from '../../../locale'; +import axios from '~/lib/utils/axios_utils'; +import { __ } from '~/locale'; export default class PayloadDownloader { constructor(trigger) { diff --git a/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js b/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js index c017cf0afa2..ae08806fe4c 100644 --- a/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js +++ b/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js @@ -1,6 +1,6 @@ import createFlash from '~/flash'; -import axios from '../../../lib/utils/axios_utils'; -import { __ } from '../../../locale'; +import axios from '~/lib/utils/axios_utils'; +import { __ } from '~/locale'; export default class PayloadPreviewer { constructor(trigger) { diff --git a/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js b/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js index 70b896f6372..a50d8de0e88 100644 --- a/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js +++ b/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js @@ -23,6 +23,7 @@ export default function initSignupRestrictions(elementSelector = '#js-signup-for return new Vue({ el, + name: 'SignupRestrictions', provide: { ...parsedDataset, }, diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js index 2a7e6a45cdd..18ba89f8856 100644 --- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js +++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js @@ -2,52 +2,41 @@ import $ from 'jquery'; import { debounce } from 'lodash'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { textColorForBackground } from '~/lib/utils/color_utils'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import { __ } from '~/locale'; export default () => { - const $broadcastMessageColor = $('.js-broadcast-message-color'); + const $broadcastMessageTheme = $('.js-broadcast-message-theme'); const $broadcastMessageType = $('.js-broadcast-message-type'); - const $broadcastBannerMessagePreview = $('.js-broadcast-banner-message-preview'); + const $broadcastBannerMessagePreview = $('.js-broadcast-banner-message-preview [role="alert"]'); const $broadcastMessage = $('.js-broadcast-message-message'); - const $jsBroadcastMessagePreview = $('.js-broadcast-message-preview'); + const $jsBroadcastMessagePreview = $('#broadcast-message-preview'); const reloadPreview = function reloadPreview() { const previewPath = $broadcastMessage.data('previewPath'); const message = $broadcastMessage.val(); const type = $broadcastMessageType.val(); - - if (message === '') { - $jsBroadcastMessagePreview.text(__('Your message here')); - } else { - axios - .post(previewPath, { - broadcast_message: { - message, - broadcast_type: type, - }, - }) - .then(({ data }) => { - $jsBroadcastMessagePreview.html(data.message); - }) - .catch(() => - createFlash({ - message: __('An error occurred while rendering preview broadcast message'), - }), - ); - } + const theme = $broadcastMessageTheme.val(); + + axios + .post(previewPath, { + broadcast_message: { + message, + broadcast_type: type, + theme, + }, + }) + .then(({ data }) => { + $jsBroadcastMessagePreview.html(data); + }) + .catch(() => + createFlash({ + message: __('An error occurred while rendering preview broadcast message'), + }), + ); }; - $broadcastMessageColor.on('input', function onMessageColorInput() { - const previewColor = $(this).val(); - $broadcastBannerMessagePreview.css('background-color', previewColor); - }); - - $('input#broadcast_message_font').on('input', function onMessageFontInput() { - const previewColor = $(this).val(); - $broadcastBannerMessagePreview.css('color', previewColor); - }); + $broadcastMessageTheme.on('change', reloadPreview); $broadcastMessageType.on('change', () => { const $broadcastMessageColorFormGroup = $('.js-broadcast-message-background-color-form-group'); @@ -68,37 +57,4 @@ export default () => { reloadPreview(); }, DEFAULT_DEBOUNCE_AND_THROTTLE_MS), ); - - const updateColorPreview = () => { - const selectedBackgroundColor = $broadcastMessageColor.val(); - const contrastTextColor = textColorForBackground(selectedBackgroundColor); - - // save contrastTextColor to hidden input field - $('input.text-font-color').val(contrastTextColor); - - // Updates the preview color with the hex-color input - const selectedColorStyle = { - backgroundColor: selectedBackgroundColor, - color: contrastTextColor, - }; - - $('.label-color-preview').css(selectedColorStyle); - - return $jsBroadcastMessagePreview.css(selectedColorStyle); - }; - - const setSuggestedColor = (e) => { - const color = $(e.currentTarget).data('color'); - $broadcastMessageColor - .val(color) - // Notify the form, that color has changed - .trigger('input'); - // Only banner supports colors - if ($broadcastMessageType === 'banner') { - updateColorPreview(); - } - return e.preventDefault(); - }; - - $(document).on('click', '.suggest-colors a', setSuggestedColor); }; diff --git a/app/assets/javascripts/pages/admin/groups/new/index.js b/app/assets/javascripts/pages/admin/groups/new/index.js index 1630cfb8253..710d2d72f4c 100644 --- a/app/assets/javascripts/pages/admin/groups/new/index.js +++ b/app/assets/javascripts/pages/admin/groups/new/index.js @@ -1,6 +1,6 @@ import initFilePickers from '~/file_pickers'; -import BindInOut from '../../../../behaviors/bind_in_out'; -import Group from '../../../../group'; +import BindInOut from '~/behaviors/bind_in_out'; +import Group from '~/group'; (() => { BindInOut.initAll(); diff --git a/app/assets/javascripts/pages/admin/index.js b/app/assets/javascripts/pages/admin/index.js index f0f85b82e2b..a249864fa36 100644 --- a/app/assets/javascripts/pages/admin/index.js +++ b/app/assets/javascripts/pages/admin/index.js @@ -1,6 +1,6 @@ import initGitlabVersionCheck from '~/gitlab_version_check'; -import initAdminStatisticsPanel from '../../admin/statistics_panel/index'; -import initVueAlerts from '../../vue_alerts'; +import initAdminStatisticsPanel from '~/admin/statistics_panel/index'; +import initVueAlerts from '~/vue_alerts'; import initAdmin from './admin'; initVueAlerts(); diff --git a/app/assets/javascripts/pages/groups/clusters/index/index.js b/app/assets/javascripts/pages/groups/clusters/index/index.js index a99e0dfa4f0..a1ba920b322 100644 --- a/app/assets/javascripts/pages/groups/clusters/index/index.js +++ b/app/assets/javascripts/pages/groups/clusters/index/index.js @@ -1,8 +1,6 @@ import initClustersListApp from '~/clusters_list'; import PersistentUserCallout from '~/persistent_user_callout'; -document.addEventListener('DOMContentLoaded', () => { - const callout = document.querySelector('.gcp-signup-offer'); - PersistentUserCallout.factory(callout); - initClustersListApp(); -}); +const callout = document.querySelector('.gcp-signup-offer'); +PersistentUserCallout.factory(callout); +initClustersListApp(); diff --git a/app/assets/javascripts/pages/groups/crm/contacts/index.js b/app/assets/javascripts/pages/groups/crm/contacts/index.js index a595246957f..6af47621c1d 100644 --- a/app/assets/javascripts/pages/groups/crm/contacts/index.js +++ b/app/assets/javascripts/pages/groups/crm/contacts/index.js @@ -1,3 +1,3 @@ -import initCrmContactsApp from '~/crm/contacts_bundle'; +import initCrmContactsApp from '~/crm/contacts/bundle'; initCrmContactsApp(); diff --git a/app/assets/javascripts/pages/groups/crm/organizations/index.js b/app/assets/javascripts/pages/groups/crm/organizations/index.js index 16479b43d52..2ad0904688e 100644 --- a/app/assets/javascripts/pages/groups/crm/organizations/index.js +++ b/app/assets/javascripts/pages/groups/crm/organizations/index.js @@ -1,3 +1,3 @@ -import initCrmOrganizationsApp from '~/crm/organizations_bundle'; +import initCrmOrganizationsApp from '~/crm/organizations/bundle'; initCrmOrganizationsApp(); diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js index 96487e14e30..58ca195d7b9 100644 --- a/app/assets/javascripts/pages/groups/edit/index.js +++ b/app/assets/javascripts/pages/groups/edit/index.js @@ -10,21 +10,19 @@ import initSearchSettings from '~/search_settings'; import initSettingsPanels from '~/settings_panels'; import initConfirmDanger from '~/init_confirm_danger'; -document.addEventListener('DOMContentLoaded', () => { - initFilePickers(); - initConfirmDanger(); - initSettingsPanels(); - initTransferGroupForm(); - dirtySubmitFactory( - document.querySelectorAll('.js-general-settings-form, .js-general-permissions-form'), - ); - mountBadgeSettings(GROUP_BADGE); +initFilePickers(); +initConfirmDanger(); +initSettingsPanels(); +initTransferGroupForm(); +dirtySubmitFactory( + document.querySelectorAll('.js-general-settings-form, .js-general-permissions-form'), +); +mountBadgeSettings(GROUP_BADGE); - // Initialize Subgroups selector - groupsSelect(); +// Initialize Subgroups selector +groupsSelect(); - projectSelect(); +projectSelect(); - initSearchSettings(); - initCascadingSettingsLockPopovers(); -}); +initSearchSettings(); +initCascadingSettingsLockPopovers(); diff --git a/app/assets/javascripts/pages/groups/group_members/index.js b/app/assets/javascripts/pages/groups/group_members/index.js index 280b544af3c..79ac31f1659 100644 --- a/app/assets/javascripts/pages/groups/group_members/index.js +++ b/app/assets/javascripts/pages/groups/group_members/index.js @@ -12,9 +12,16 @@ const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions']; initMembersApp(document.querySelector('.js-group-members-list-app'), { [MEMBER_TYPES.user]: { - tableFields: SHARED_FIELDS.concat(['source', 'granted']), + tableFields: SHARED_FIELDS.concat(['source', 'granted', 'userCreatedAt', 'lastActivityOn']), tableAttrs: { tr: { 'data-qa-selector': 'member_row' } }, - tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'], + tableSortableFields: [ + 'account', + 'granted', + 'maxRole', + 'lastSignIn', + 'userCreatedAt', + 'lastActivityOn', + ], requestFormatter: groupMemberRequestFormatter, filteredSearchBar: { show: true, @@ -25,12 +32,25 @@ initMembersApp(document.querySelector('.js-group-members-list-app'), { }, }, [MEMBER_TYPES.group]: { - tableFields: SHARED_FIELDS.concat('granted'), + tableFields: gon?.features?.groupMemberInheritedGroup + ? SHARED_FIELDS.concat(['source', 'granted']) + : SHARED_FIELDS.concat(['granted']), tableAttrs: { table: { 'data-qa-selector': 'groups_list' }, tr: { 'data-qa-selector': 'group_row' }, }, requestFormatter: groupLinkRequestFormatter, + ...(gon?.features?.groupMemberInheritedGroup + ? { + filteredSearchBar: { + show: true, + tokens: ['with_inherited_permissions'], + searchParam: 'search_groups', + placeholder: s__('Members|Filter groups'), + recentSearchesStorageKey: 'group_links_members', + }, + } + : {}), }, [MEMBER_TYPES.invite]: { tableFields: SHARED_FIELDS.concat('invited'), diff --git a/app/assets/javascripts/pages/groups/harbor/repositories/index.js b/app/assets/javascripts/pages/groups/harbor/repositories/index.js new file mode 100644 index 00000000000..0ecce44be54 --- /dev/null +++ b/app/assets/javascripts/pages/groups/harbor/repositories/index.js @@ -0,0 +1,8 @@ +import HarborRegistryExplorer from '~/packages_and_registries/harbor_registry/index'; + +const explorer = HarborRegistryExplorer('js-harbor-registry-list-group'); + +if (explorer) { + explorer.attachBreadcrumb(); + explorer.attachMainComponent(); +} 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 index 0ec382983a5..9a4054eb110 100644 --- 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 @@ -111,7 +111,7 @@ export default { }, getFullDestinationUrl(params) { - return joinPaths(gon.relative_url_root || '', this.getDestinationUrl(params)); + return joinPaths(gon.relative_url_root || '', '/', this.getDestinationUrl(params)); }, }, @@ -161,7 +161,7 @@ export default { > </template> <template #row-details="{ item }"> - <pre>{{ item.failures }}</pre> + <pre><code>{{ item.failures }}</code></pre> </template> </gl-table> <pagination-bar diff --git a/app/assets/javascripts/pages/import/history/components/import_error_details.vue b/app/assets/javascripts/pages/import/history/components/import_error_details.vue new file mode 100644 index 00000000000..33ba73317f8 --- /dev/null +++ b/app/assets/javascripts/pages/import/history/components/import_error_details.vue @@ -0,0 +1,43 @@ +<script> +import { GlLoadingIcon } from '@gitlab/ui'; +import API from '~/api'; +import { createAlert } from '~/flash'; +import { DEFAULT_ERROR } from '../utils/error_messages'; + +export default { + components: { + GlLoadingIcon, + }, + props: { + id: { + type: Number, + required: true, + }, + }, + data() { + return { + loading: true, + error: null, + }; + }, + async mounted() { + try { + const { + data: { import_error: importError }, + } = await API.project(this.id); + this.error = importError; + } catch (e) { + createAlert({ message: DEFAULT_ERROR }); + this.error = null; + } finally { + this.loading = false; + } + }, +}; +</script> +<template> + <gl-loading-icon v-if="loading" size="md" /> + <pre + v-else + ><code>{{ error || s__('BulkImport|No additional information provided.') }}</code></pre> +</template> diff --git a/app/assets/javascripts/pages/import/history/components/import_history_app.vue b/app/assets/javascripts/pages/import/history/components/import_history_app.vue new file mode 100644 index 00000000000..557e25f66e2 --- /dev/null +++ b/app/assets/javascripts/pages/import/history/components/import_history_app.vue @@ -0,0 +1,199 @@ +<script> +import { GlButton, GlEmptyState, GlIcon, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui'; +import { s__, __ } from '~/locale'; +import createFlash from '~/flash'; +import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; +import { getProjects } from '~/rest_api'; +import ImportStatus from '~/import_entities/components/import_status.vue'; +import PaginationBar from '~/vue_shared/components/pagination_bar/pagination_bar.vue'; +import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; +import { DEFAULT_ERROR } from '../utils/error_messages'; +import ImportErrorDetails from './import_error_details.vue'; + +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, + GlIcon, + GlLink, + GlLoadingIcon, + GlTable, + PaginationBar, + ImportStatus, + ImportErrorDetails, + TimeAgo, + }, + + inject: ['assets'], + + data() { + return { + loading: true, + historyItems: [], + paginationConfig: { + page: 1, + perPage: DEFAULT_PER_PAGE, + }, + pageInfo: {}, + }; + }, + + fields: [ + tableCell({ + key: 'source', + label: s__('BulkImport|Source'), + thClass: `${DEFAULT_TH_CLASSES} gl-w-30p`, + }), + tableCell({ + key: 'destination', + label: s__('BulkImport|Destination'), + 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 getProjects(undefined, { + imported: true, + simple: false, + 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; + } + }, + + hasHttpProtocol(url) { + try { + const parsedUrl = new URL(url); + return ['http:', 'https:'].includes(parsedUrl.protocol); + } catch (e) { + return false; + } + }, + + setPageSize(size) { + this.paginationConfig.perPage = size; + this.paginationConfig.page = 1; + }, + }, +}; +</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="assets.gitlabLogo" class="gl-w-6 gl-h-6 gl-mb-2 gl-display-inline gl-mr-2" /> + {{ s__('BulkImport|Project 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 projects 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(source)="{ item }"> + <template v-if="item.import_url"> + <gl-link + v-if="hasHttpProtocol(item.import_url)" + :href="item.import_url" + target="_blank" + > + {{ item.import_url }} + <gl-icon name="external-link" class="gl-vertical-align-middle" /> + </gl-link> + <span v-else>{{ item.import_url }}</span> + </template> + <span v-else>{{ + s__('BulkImport|Template / File-based import / GitLab Migration') + }}</span> + </template> + <template #cell(destination)="{ item }"> + <gl-link :href="item.http_url_to_repo"> + {{ item.path_with_namespace }} + </gl-link> + </template> + <template #cell(created_at)="{ value }"> + <time-ago :time="value" /> + </template> + <template #cell(status)="{ item, toggleDetails, detailsShowing }"> + <import-status :status="item.import_status" class="gl-display-inline-block gl-w-13" /> + <gl-button + v-if="item.import_status === 'failed'" + class="gl-ml-3" + :selected="detailsShowing" + @click="toggleDetails" + >{{ __('Details') }}</gl-button + > + </template> + <template #row-details="{ item }"> + <import-error-details :id="item.id" /> + </template> + </gl-table> + <pagination-bar + :page-info="pageInfo" + class="gl-m-0 gl-mt-3" + @set-page="paginationConfig.page = $event" + @set-page-size="setPageSize" + /> + </template> + </div> +</template> diff --git a/app/assets/javascripts/pages/import/history/index.js b/app/assets/javascripts/pages/import/history/index.js new file mode 100644 index 00000000000..d540272c266 --- /dev/null +++ b/app/assets/javascripts/pages/import/history/index.js @@ -0,0 +1,21 @@ +import Vue from 'vue'; +import ImportHistoryApp from './components/import_history_app.vue'; + +function mountImportHistoryApp(mountElement) { + if (!mountElement) return undefined; + + return new Vue({ + el: mountElement, + name: 'ImportHistoryRoot', + provide: { + assets: { + gitlabLogo: mountElement.dataset.logo, + }, + }, + render(createElement) { + return createElement(ImportHistoryApp); + }, + }); +} + +mountImportHistoryApp(document.querySelector('#import-history-mount-element')); diff --git a/app/assets/javascripts/pages/import/history/utils/error_messages.js b/app/assets/javascripts/pages/import/history/utils/error_messages.js new file mode 100644 index 00000000000..24669e22ade --- /dev/null +++ b/app/assets/javascripts/pages/import/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/preferences/show/index.js b/app/assets/javascripts/pages/profiles/preferences/show/index.js index d489ed80f46..76939434680 100644 --- a/app/assets/javascripts/pages/profiles/preferences/show/index.js +++ b/app/assets/javascripts/pages/profiles/preferences/show/index.js @@ -1,3 +1,5 @@ import initProfilePreferences from '~/profile/preferences/profile_preferences_bundle'; +import initProfilePreferencesDiffsColors from '~/profile/preferences/profile_preferences_diffs_colors'; -document.addEventListener('DOMContentLoaded', initProfilePreferences); +initProfilePreferences(); +initProfilePreferencesDiffsColors(); diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js index c6a76df7bde..eca3cf7ab13 100644 --- a/app/assets/javascripts/pages/projects/commit/show/index.js +++ b/app/assets/javascripts/pages/projects/commit/show/index.js @@ -1,5 +1,6 @@ /* eslint-disable no-new */ import $ from 'jquery'; +import Vue from 'vue'; import loadAwardsHandler from '~/awards_handler'; import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import Diff from '~/diff'; @@ -14,6 +15,7 @@ import { initCommitBoxInfo } from '~/projects/commit_box/info'; import syntaxHighlight from '~/syntax_highlight'; import ZenMode from '~/zen_mode'; import '~/sourcegraph/load'; +import DiffStats from '~/diffs/components/diff_stats.vue'; const hasPerfBar = document.querySelector('.with-performance-bar'); const performanceHeight = hasPerfBar ? 35 : 0; @@ -25,6 +27,33 @@ initCommitBoxInfo(); initDeprecatedNotes(); +const loadDiffStats = () => { + const diffStatsElements = document.querySelectorAll('#js-diff-stats'); + + if (diffStatsElements.length) { + diffStatsElements.forEach((diffStatsEl) => { + const { addedLines, removedLines, oldSize, newSize, viewerName } = diffStatsEl.dataset; + + new Vue({ + el: diffStatsEl, + render(createElement) { + return createElement(DiffStats, { + props: { + diffFile: { + old_size: oldSize, + new_size: newSize, + viewer: { name: viewerName }, + }, + addedLines: Number(addedLines), + removedLines: Number(removedLines), + }, + }); + }, + }); + }); + } +}; + const filesContainer = $('.js-diffs-batch'); if (filesContainer.length) { @@ -37,12 +66,15 @@ if (filesContainer.length) { syntaxHighlight(filesContainer); handleLocationHash(); new Diff(); + loadDiffStats(); }) .catch(() => { createFlash({ message: __('An error occurred while retrieving diff files') }); }); } else { new Diff(); + loadDiffStats(); } + loadAwardsHandler(); initCommitActions(); diff --git a/app/assets/javascripts/pages/projects/harbor/repositories/index.js b/app/assets/javascripts/pages/projects/harbor/repositories/index.js new file mode 100644 index 00000000000..efbe24ac346 --- /dev/null +++ b/app/assets/javascripts/pages/projects/harbor/repositories/index.js @@ -0,0 +1,8 @@ +import HarborRegistryExplorer from '~/packages_and_registries/harbor_registry/index'; + +const explorer = HarborRegistryExplorer('js-harbor-registry-list-project'); + +if (explorer) { + explorer.attachBreadcrumb(); + explorer.attachMainComponent(); +} diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js index 8ec6e5e66b3..7380055cbbf 100644 --- a/app/assets/javascripts/pages/projects/index.js +++ b/app/assets/javascripts/pages/projects/index.js @@ -1,5 +1,5 @@ -import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation'; -import initTerraformNotification from '../../projects/terraform_notification'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; +import initTerraformNotification from '~/projects/terraform_notification'; import { initSidebarTracking } from '../shared/nav/sidebar_tracking'; import Project from './project'; diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue index 67962d69fa5..db9ef4df8af 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue @@ -127,8 +127,12 @@ export default { </p> <gl-progress-bar :value="progressValue" :max="$options.maxValue" /> </div> - <div class="row row-cols-1 row-cols-md-3 gl-mt-5"> - <div v-for="section in $options.actionSections" :key="section" class="col gl-mb-6"> + <div class="row"> + <div + v-for="section in $options.actionSections" + :key="section" + class="gl-mt-5 col-sm-12 col-mb-6 col-lg-4" + > <learn-gitlab-section-card :section="section" :svg="svgFor(section)" diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue index 6a196687a76..e8f0e6c47ee 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue @@ -34,17 +34,23 @@ export default { }; </script> <template> - <gl-card class="gl-pt-0 learn-gitlab-section-card"> - <div class="learn-gitlab-section-card-header"> + <gl-card + class="gl-pt-0 h-100" + header-class="gl-bg-white gl-border-0 gl-pb-0" + body-class="gl-pt-0" + > + <template #header> <img :src="svg" /> <h2 class="gl-font-lg gl-mb-3">{{ $options.i18n[section].title }}</h2> <p class="gl-text-gray-700 gl-mb-6">{{ $options.i18n[section].description }}</p> - </div> - <learn-gitlab-section-link - v-for="[action, value] in sortedActions" - :key="action" - :action="action" - :value="value" - /> + </template> + <template #default> + <learn-gitlab-section-link + v-for="[action, value] in sortedActions" + :key="action" + :action="action" + :value="value" + /> + </template> </gl-card> </template> diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue index 573f996a254..1667f2c3576 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue @@ -1,16 +1,25 @@ <script> -import { GlLink, GlIcon } from '@gitlab/ui'; +import { GlLink, GlIcon, GlButton, GlTooltipDirective as GlTooltip } from '@gitlab/ui'; +import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue'; import { isExperimentVariant } from '~/experimentation/utils'; import eventHub from '~/invite_members/event_hub'; -import { s__ } from '~/locale'; +import { s__, __ } from '~/locale'; import { ACTION_LABELS } from '../constants'; export default { name: 'LearnGitlabSectionLink', - components: { GlLink, GlIcon }, + components: { + GlLink, + GlIcon, + GlButton, + GitlabExperiment, + }, + directives: { + GlTooltip, + }, i18n: { - ACTION_LABELS, trialOnly: s__('LearnGitlab|Trial only'), + watchHow: __('Watch how'), }, props: { action: { @@ -23,6 +32,9 @@ export default { }, }, computed: { + linkTitle() { + return ACTION_LABELS[this.action].title; + }, trialOnly() { return ACTION_LABELS[this.action].trialRequired; }, @@ -34,6 +46,9 @@ export default { openInNewTab() { return ACTION_LABELS[this.action]?.openInNewTab === true || this.value.openInNewTab === true; }, + linkToVideoTutorial() { + return ACTION_LABELS[this.action].videoTutorial; + }, }, methods: { openModal() { @@ -44,32 +59,55 @@ export default { </script> <template> <div class="gl-mb-4"> - <span v-if="value.completed" class="gl-text-green-500"> - <gl-icon name="check-circle-filled" :size="16" data-testid="completed-icon" /> - {{ $options.i18n.ACTION_LABELS[action].title }} - </span> - <gl-link - v-else-if="showInviteModalLink" - data-track-action="click_link" - :data-track-label="$options.i18n.ACTION_LABELS[action].title" - data-track-property="Growth::Activation::Experiment::InviteForHelpContinuousOnboarding" - data-testid="invite-for-help-continuous-onboarding-experiment-link" - @click="openModal" - > - {{ $options.i18n.ACTION_LABELS[action].title }} - </gl-link> - <gl-link - v-else - :target="openInNewTab ? '_blank' : '_self'" - :href="value.url" - data-testid="uncompleted-learn-gitlab-link" - data-track-action="click_link" - :data-track-label="$options.i18n.ACTION_LABELS[action].title" - > - {{ $options.i18n.ACTION_LABELS[action].title }} - </gl-link> - <span v-if="trialOnly" class="gl-font-style-italic gl-text-gray-500" data-testid="trial-only"> - - {{ $options.i18n.trialOnly }} - </span> + <div v-if="trialOnly" class="gl-font-style-italic gl-text-gray-500" data-testid="trial-only"> + {{ $options.i18n.trialOnly }} + </div> + <div class="flex align-items-center"> + <span v-if="value.completed" class="gl-text-green-500"> + <gl-icon name="check-circle-filled" :size="16" data-testid="completed-icon" /> + {{ linkTitle }} + </span> + <gl-link + v-else-if="showInviteModalLink" + data-track-action="click_link" + :data-track-label="linkTitle" + data-track-property="Growth::Activation::Experiment::InviteForHelpContinuousOnboarding" + data-testid="invite-for-help-continuous-onboarding-experiment-link" + @click="openModal" + > + {{ linkTitle }} + </gl-link> + <gl-link + v-else + :target="openInNewTab ? '_blank' : '_self'" + :href="value.url" + data-testid="uncompleted-learn-gitlab-link" + data-track-action="click_link" + :data-track-label="linkTitle" + > + {{ linkTitle }} + </gl-link> + <gitlab-experiment name="video_tutorials_continuous_onboarding"> + <template #control></template> + <template #candidate> + <gl-button + v-if="linkToVideoTutorial" + v-gl-tooltip + category="tertiary" + icon="live-preview" + :title="$options.i18n.watchHow" + :aria-label="$options.i18n.watchHow" + :href="linkToVideoTutorial" + target="_blank" + class="ml-auto" + data-testid="video-tutorial-link" + data-track-action="click_video_link" + :data-track-label="linkTitle" + data-track-property="Growth::Conversion::Experiment::LearnGitLab" + data-track-experiment="video_tutorials_continuous_onboarding" + /> + </template> + </gitlab-experiment> + </div> </div> </template> diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js index 1887c48dd1b..9ba5e17237a 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js +++ b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js @@ -40,6 +40,7 @@ export const ACTION_LABELS = { trialRequired: true, section: 'workspace', position: 4, + videoTutorial: 'https://vimeo.com/670896787', }, requiredMrApprovalsEnabled: { title: s__('LearnGitLab|Add merge request approval'), @@ -48,6 +49,7 @@ export const ACTION_LABELS = { trialRequired: true, section: 'workspace', position: 5, + videoTutorial: 'https://vimeo.com/670904904', }, mergeRequestCreated: { title: s__('LearnGitLab|Submit a merge request'), diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js b/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js index 63357ea9c72..af4a6f8a0c9 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js +++ b/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import initInviteMembersModal from '~/invite_members/init_invite_members_modal'; +import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import LearnGitlab from '../components/learn_gitlab.vue'; @@ -24,5 +25,7 @@ function initLearnGitlab() { }); } -initLearnGitlab(); initInviteMembersModal(); +initInviteMembersTrigger(); + +initLearnGitlab(); diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js index c548ea9bb80..0e0c1475eda 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js +++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js @@ -1,6 +1,5 @@ import Vue from 'vue'; 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 '~/issuable'; @@ -8,23 +7,16 @@ import StatusBox from '~/issuable/components/status_box.vue'; import createDefaultClient from '~/lib/graphql'; import initSourcegraph from '~/sourcegraph'; import ZenMode from '~/zen_mode'; +import initAwardsApp from '~/emoji/awards_app'; import getStateQuery from './queries/get_state.query.graphql'; export default function initMergeRequestShow() { - const awardEmojiEl = document.getElementById('js-vue-awards-block'); - new ZenMode(); // eslint-disable-line no-new initPipelineCountListener(document.querySelector('#commit-pipeline-table-view')); new ShortcutsIssuable(true); // eslint-disable-line no-new initSourcegraph(); initIssuableSidebar(); - if (awardEmojiEl) { - import('~/emoji/awards_app') - .then((m) => m.default(awardEmojiEl)) - .catch(() => {}); - } else { - loadAwardsHandler(); - } + initAwardsApp(document.getElementById('js-vue-awards-block')); const el = document.querySelector('.js-mr-status-box'); const apolloProvider = new VueApollo({ diff --git a/app/assets/javascripts/pages/projects/network/network.js b/app/assets/javascripts/pages/projects/network/network.js index 5f2014f1631..b88127384dc 100644 --- a/app/assets/javascripts/pages/projects/network/network.js +++ b/app/assets/javascripts/pages/projects/network/network.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import BranchGraph from '../../../network/branch_graph'; +import BranchGraph from '~/network/branch_graph'; const vph = $(window).height() - 250; diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue index ee70ff858be..37e8a316ee4 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue @@ -2,8 +2,7 @@ import { GlButton } from '@gitlab/ui'; import Vue from 'vue'; import { getCookie, setCookie, parseBoolean } from '~/lib/utils/common_utils'; - -import Translate from '../../../../../vue_shared/translate'; +import Translate from '~/vue_shared/translate'; Vue.use(Translate); diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js index 9c039a6be81..5dae812bbcb 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js @@ -3,9 +3,9 @@ import Vue from 'vue'; import { __ } from '~/locale'; import RefSelector from '~/ref/components/ref_selector.vue'; import { REF_TYPE_BRANCHES, REF_TYPE_TAGS } from '~/ref/constants'; -import setupNativeFormVariableList from '../../../../ci_variable_list/native_form_variable_list'; -import GlFieldErrors from '../../../../gl_field_errors'; -import Translate from '../../../../vue_shared/translate'; +import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list'; +import GlFieldErrors from '~/gl_field_errors'; +import Translate from '~/vue_shared/translate'; import intervalPatternInput from './components/interval_pattern_input.vue'; import TimezoneDropdown from './components/timezone_dropdown'; @@ -33,13 +33,7 @@ function initIntervalPatternInput() { } function getEnabledRefTypes() { - const refTypes = [REF_TYPE_BRANCHES]; - - if (gon.features.pipelineSchedulesWithTags) { - refTypes.push(REF_TYPE_TAGS); - } - - return refTypes; + return [REF_TYPE_BRANCHES, REF_TYPE_TAGS]; } function initTargetRefDropdown() { @@ -61,9 +55,7 @@ function initTargetRefDropdown() { value: $refField.value, useSymbolicRefNames: true, translations: { - dropdownHeader: gon.features.pipelineSchedulesWithTags - ? __('Select target branch or tag') - : __('Select target branch'), + dropdownHeader: __('Select target branch or tag'), }, }, class: 'gl-w-full', diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 0c17bf2f344..4f57e1308df 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -9,7 +9,7 @@ import axios from '~/lib/utils/axios_utils'; import { serializeForm } from '~/lib/utils/forms'; import { mergeUrlParams } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; -import projectSelect from '../../project_select'; +import projectSelect from '~/project_select'; export default class Project { constructor() { diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js index 2c0394dc12c..bf4fb5f3b7e 100644 --- a/app/assets/javascripts/pages/projects/project_members/index.js +++ b/app/assets/javascripts/pages/projects/project_members/index.js @@ -18,9 +18,16 @@ initInviteGroupTrigger(); const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions']; initMembersApp(document.querySelector('.js-project-members-list-app'), { [MEMBER_TYPES.user]: { - tableFields: SHARED_FIELDS.concat(['source', 'granted']), + tableFields: SHARED_FIELDS.concat(['source', 'granted', 'userCreatedAt', 'lastActivityOn']), tableAttrs: { tr: { 'data-qa-selector': 'member_row' } }, - tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'], + tableSortableFields: [ + 'account', + 'granted', + 'maxRole', + 'lastSignIn', + 'userCreatedAt', + 'lastActivityOn', + ], requestFormatter: projectMemberRequestFormatter, filteredSearchBar: { show: true, diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js index e88dbf20e1b..43ab829f5f9 100644 --- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js @@ -10,36 +10,34 @@ import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_to import initSettingsPanels from '~/settings_panels'; import { initTokenAccess } from '~/token_access'; -document.addEventListener('DOMContentLoaded', () => { - // Initialize expandable settings panels - initSettingsPanels(); +// Initialize expandable settings panels +initSettingsPanels(); - const runnerToken = document.querySelector('.js-secret-runner-token'); - if (runnerToken) { - const runnerTokenSecretValue = new SecretValues({ - container: runnerToken, - }); - runnerTokenSecretValue.init(); - } - - initVariableList(); - - // hide extra auto devops settings based checkbox state - const autoDevOpsExtraSettings = document.querySelector('.js-extra-settings'); - const instanceDefaultBadge = document.querySelector('.js-instance-default-badge'); - document.querySelector('.js-toggle-extra-settings').addEventListener('click', (event) => { - const { target } = event; - if (instanceDefaultBadge) instanceDefaultBadge.style.display = 'none'; - autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked); +const runnerToken = document.querySelector('.js-secret-runner-token'); +if (runnerToken) { + const runnerTokenSecretValue = new SecretValues({ + container: runnerToken, }); + runnerTokenSecretValue.init(); +} - registrySettingsApp(); - initDeployFreeze(); +initVariableList(); - initSettingsPipelinesTriggers(); - initArtifactsSettings(); - initSharedRunnersToggle(); - initInstallRunner(); - initRunnerAwsDeployments(); - initTokenAccess(); +// hide extra auto devops settings based checkbox state +const autoDevOpsExtraSettings = document.querySelector('.js-extra-settings'); +const instanceDefaultBadge = document.querySelector('.js-instance-default-badge'); +document.querySelector('.js-toggle-extra-settings').addEventListener('click', (event) => { + const { target } = event; + if (instanceDefaultBadge) instanceDefaultBadge.style.display = 'none'; + autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked); }); + +registrySettingsApp(); +initDeployFreeze(); + +initSettingsPipelinesTriggers(); +initArtifactsSettings(); +initSharedRunnersToggle(); +initInstallRunner(); +initRunnerAwsDeployments(); +initTokenAccess(); diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js index e90954c14c5..d45052d76f4 100644 --- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js @@ -1,9 +1,7 @@ import MirrorRepos from '~/mirrors/mirror_repos'; import initForm from '../form'; -document.addEventListener('DOMContentLoaded', () => { - initForm(); +initForm(); - const mirrorReposContainer = document.querySelector('.js-mirror-settings'); - if (mirrorReposContainer) new MirrorRepos(mirrorReposContainer).init(); -}); +const mirrorReposContainer = document.querySelector('.js-mirror-settings'); +if (mirrorReposContainer) new MirrorRepos(mirrorReposContainer).init(); diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue index 9fb8be3fdb9..b2d32c2c943 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue @@ -44,6 +44,15 @@ export default { }, }, computed: { + internalValue: { + get() { + return this.value; + }, + set(value) { + this.$emit('change', value); + }, + }, + featureEnabled() { return this.value !== 0; }, @@ -68,10 +77,6 @@ export default { this.$emit('change', firstOptionValue); } }, - - selectOption(e) { - this.$emit('change', Number(e.target.value)); - }, }, }; </script> @@ -93,15 +98,14 @@ export default { /> <div class="select-wrapper gl-flex-grow-1"> <select + v-model="internalValue" :disabled="displaySelectInput" class="form-control project-repo-select select-control" - @change="selectOption" > <option v-for="[optionValue, optionName] in displayOptions" :key="optionValue" :value="optionValue" - :selected="optionValue === value" > {{ optionName }} </option> diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 184bda4410f..03bab0fa773 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -9,7 +9,6 @@ import { featureAccessLevelMembers, featureAccessLevelEveryone, featureAccessLevel, - featureAccessLevelNone, CVE_ID_REQUEST_BUTTON_I18N, featureAccessLevelDescriptions, } from '../constants'; @@ -225,8 +224,6 @@ export default { }, operationsFeatureAccessLevelOptions() { - if (!this.operationsEnabled) return [featureAccessLevelNone]; - return this.featureAccessLevelOptions.filter( ([value]) => value <= this.operationsAccessLevel, ); @@ -251,10 +248,6 @@ export default { return options; }, - metricsOptionsDropdownDisabled() { - return this.operationsFeatureAccessLevelOptions.length < 2 || !this.operationsEnabled; - }, - operationsEnabled() { return this.operationsAccessLevel > featureAccessLevel.NOT_ENABLED; }, @@ -392,6 +385,15 @@ export default { else if (oldValue === featureAccessLevel.NOT_ENABLED) toggleHiddenClassBySelector('.merge-requests-feature', false); }, + + operationsAccessLevel(value, oldValue) { + if (value < oldValue) { + // sub-features cannot have more permissive access level + this.metricsDashboardAccessLevel = Math.min(this.metricsDashboardAccessLevel, value); + } else if (oldValue === 0) { + this.metricsDashboardAccessLevel = value; + } + }, }, methods: { @@ -590,7 +592,9 @@ export default { :help-path="packagesHelpPath" :label="$options.i18n.packagesLabel" :help-text=" - s__('ProjectSettings|Every project can have its own space to store its packages.') + s__( + 'ProjectSettings|Every project can have its own space to store its packages. Note: The Package Registry is always visible when a project is public.', + ) " > <gl-toggle diff --git a/app/assets/javascripts/pages/projects/snippets/show/index.js b/app/assets/javascripts/pages/projects/snippets/show/index.js index c719601ee0b..77baa6d77a5 100644 --- a/app/assets/javascripts/pages/projects/snippets/show/index.js +++ b/app/assets/javascripts/pages/projects/snippets/show/index.js @@ -1,9 +1,4 @@ import '~/snippet/snippet_show'; +import initAwardsApp from '~/emoji/awards_app'; -const awardEmojiEl = document.getElementById('js-vue-awards-block'); - -if (awardEmojiEl) { - import('~/emoji/awards_app') - .then((m) => m.default(awardEmojiEl)) - .catch(() => {}); -} +initAwardsApp(document.getElementById('js-vue-awards-block')); diff --git a/app/assets/javascripts/pages/projects/tags/new/index.js b/app/assets/javascripts/pages/projects/tags/new/index.js index b071e7a45fc..9ef1017f9f2 100644 --- a/app/assets/javascripts/pages/projects/tags/new/index.js +++ b/app/assets/javascripts/pages/projects/tags/new/index.js @@ -1,7 +1,7 @@ import $ from 'jquery'; -import GLForm from '../../../../gl_form'; -import RefSelectDropdown from '../../../../ref_select_dropdown'; -import ZenMode from '../../../../zen_mode'; +import GLForm from '~/gl_form'; +import RefSelectDropdown from '~/ref_select_dropdown'; +import ZenMode from '~/zen_mode'; new ZenMode(); // eslint-disable-line no-new new GLForm($('.tag-form')); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js index 4bb461aadad..cf7162f477d 100644 --- a/app/assets/javascripts/pages/projects/tree/show/index.js +++ b/app/assets/javascripts/pages/projects/tree/show/index.js @@ -1,8 +1,8 @@ import $ from 'jquery'; import initTree from 'ee_else_ce/repository'; import initBlob from '~/blob_edit/blob_bundle'; -import ShortcutsNavigation from '../../../../behaviors/shortcuts/shortcuts_navigation'; -import NewCommitForm from '../../../../new_commit_form'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; +import NewCommitForm from '~/new_commit_form'; new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new initBlob(); diff --git a/app/assets/javascripts/pages/projects/wikis/show/index.js b/app/assets/javascripts/pages/projects/wikis/show/index.js index c08a10122b6..7ca5f6964cd 100644 --- a/app/assets/javascripts/pages/projects/wikis/show/index.js +++ b/app/assets/javascripts/pages/projects/wikis/show/index.js @@ -1,3 +1,5 @@ +import { mountApplications } from '~/pages/shared/wikis/show'; import { mountApplications as mountEditApplications } from '~/pages/shared/wikis/async_edit'; +mountApplications(); mountEditApplications(); diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js index 8c2fd624a83..b62417cf595 100644 --- a/app/assets/javascripts/pages/sessions/new/index.js +++ b/app/assets/javascripts/pages/sessions/new/index.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import initVueAlerts from '~/vue_alerts'; -import NoEmojiValidator from '../../../emoji/no_emoji_validator'; +import NoEmojiValidator from '~/emoji/no_emoji_validator'; import LengthValidator from './length_validator'; import OAuthRememberMe from './oauth_remember_me'; import preserveUrlFragment from './preserve_url_fragment'; diff --git a/app/assets/javascripts/pages/sessions/new/length_validator.js b/app/assets/javascripts/pages/sessions/new/length_validator.js index 17acad10bc1..b2074fb1e39 100644 --- a/app/assets/javascripts/pages/sessions/new/length_validator.js +++ b/app/assets/javascripts/pages/sessions/new/length_validator.js @@ -1,4 +1,4 @@ -import InputValidator from '../../../validators/input_validator'; +import InputValidator from '~/validators/input_validator'; const errorMessageClass = 'gl-field-error'; diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue new file mode 100644 index 00000000000..7c23f60954a --- /dev/null +++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue @@ -0,0 +1,92 @@ +<script> +import { GlSkeletonLoader, GlSafeHtmlDirective, GlAlert } from '@gitlab/ui'; +import createFlash from '~/flash'; +import { __ } from '~/locale'; +import axios from '~/lib/utils/axios_utils'; +import { renderGFM } from '../render_gfm_facade'; + +export default { + components: { + GlSkeletonLoader, + GlAlert, + }, + directives: { + SafeHtml: GlSafeHtmlDirective, + }, + props: { + getWikiContentUrl: { + type: String, + required: true, + }, + }, + data() { + return { + isLoadingContent: false, + loadingContentFailed: false, + content: null, + }; + }, + mounted() { + this.loadWikiContent(); + }, + methods: { + async loadWikiContent() { + this.loadingContentFailed = false; + this.isLoadingContent = true; + + try { + const { + data: { content }, + } = await axios.get(this.getWikiContentUrl, { params: { render_html: true } }); + this.content = content; + + this.$nextTick() + .then(() => { + renderGFM(this.$refs.content); + }) + .catch(() => + createFlash({ + message: this.$options.i18n.renderingContentFailed, + }), + ); + } catch (e) { + this.loadingContentFailed = true; + } finally { + this.isLoadingContent = false; + } + }, + }, + i18n: { + loadingContentFailed: __( + 'The content for this wiki page failed to load. To fix this error, reload the page.', + ), + retryLoadingContent: __('Retry'), + renderingContentFailed: __('The content for this wiki page failed to render.'), + }, +}; +</script> +<template> + <gl-skeleton-loader v-if="isLoadingContent" :width="830" :height="113"> + <rect width="540" height="16" rx="4" /> + <rect y="49" width="701" height="16" rx="4" /> + <rect y="24" width="830" height="16" rx="4" /> + <rect y="73" width="540" height="16" rx="4" /> + </gl-skeleton-loader> + <gl-alert + v-else-if="loadingContentFailed" + :dismissible="false" + variant="danger" + :primary-button-text="$options.i18n.retryLoadingContent" + @primaryAction="loadWikiContent" + > + {{ $options.i18n.loadingContentFailed }} + </gl-alert> + <div + v-else-if="!loadingContentFailed && !isLoadingContent" + ref="content" + data-qa-selector="wiki_page_content" + data-testid="wiki_page_content" + class="js-wiki-page-content md" + v-html="content /* eslint-disable-line vue/no-v-html */" + ></div> +</template> diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue index 8ef31b9b983..024b3bc9595 100644 --- a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue +++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue @@ -1,21 +1,11 @@ <script> -import { - GlForm, - GlIcon, - GlLink, - GlButton, - GlSprintf, - GlAlert, - GlModal, - GlModalDirective, -} from '@gitlab/ui'; +import { GlForm, GlIcon, GlLink, GlButton, GlSprintf, GlAlert } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; import csrf from '~/lib/utils/csrf'; import { setUrlFragment } from '~/lib/utils/url_utility'; -import { __, s__, sprintf } from '~/locale'; +import { s__, sprintf } from '~/locale'; import Tracking from '~/tracking'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { CONTENT_EDITOR_LOADED_ACTION, SAVED_USING_CONTENT_EDITOR_ACTION, @@ -64,31 +54,6 @@ export default { ), primaryAction: s__('WikiPage|Retry'), }, - useNewEditor: { - primaryLabel: s__('WikiPage|Use the new editor'), - secondaryLabel: s__('WikiPage|Try this later'), - title: s__('WikiPage|Get a richer editing experience'), - text: s__( - "WikiPage|Try the new visual Markdown editor. Read the %{linkStart}documentation%{linkEnd} to learn what's currently supported.", - ), - }, - switchToOldEditor: { - label: s__('WikiPage|Switch me back to the classic editor.'), - helpText: s__( - "WikiPage|This editor is in beta and may not display the page's contents properly. Switching back to the classic editor will discard changes you've made in the new editor.", - ), - modal: { - title: s__('WikiPage|Are you sure you want to switch back to the classic editor?'), - primary: s__('WikiPage|Switch to classic editor'), - cancel: s__('WikiPage|Keep editing'), - text: s__( - "WikiPage|Switching to the classic editor will discard any changes you've made in the new editor.", - ), - }, - }, - feedbackTip: __( - 'Tell us your experiences with the new Markdown editor %{linkStart}in this feedback issue%{linkEnd}.', - ), }, linksHelpText: s__( 'WikiPage|To link to a (new) page, simply type %{linkExample}. More examples are in the %{linkStart}documentation%{linkEnd}.', @@ -108,7 +73,6 @@ export default { editSourceButtonText: s__('WikiPage|Edit source'), editRichTextButtonText: s__('WikiPage|Edit rich text'), }, - contentEditorFeedbackIssue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332629', components: { GlAlert, GlForm, @@ -116,24 +80,19 @@ export default { GlIcon, GlLink, GlButton, - GlModal, MarkdownField, ContentEditor: () => import( /* webpackChunkName: 'content_editor' */ '~/content_editor/components/content_editor.vue' ), }, - directives: { - GlModalDirective, - }, - mixins: [trackingMixin, glFeatureFlagMixin()], + mixins: [trackingMixin], inject: ['formatOptions', 'pageInfo'], data() { return { title: this.pageInfo.title?.trim() || '', format: this.pageInfo.format || 'markdown', content: this.pageInfo.content || '', - isContentEditorAlertDismissed: false, useContentEditor: false, commitMessage: '', isDirty: false, @@ -194,25 +153,9 @@ export default { isMarkdownFormat() { return this.format === 'markdown'; }, - showContentEditorAlert() { - return ( - !this.glFeatures.wikiSwitchBetweenContentEditorRawMarkdown && - this.isMarkdownFormat && - !this.useContentEditor && - !this.isContentEditorAlertDismissed - ); - }, - showSwitchEditingModeButton() { - return this.glFeatures.wikiSwitchBetweenContentEditorRawMarkdown && this.isMarkdownFormat; - }, displayWikiSpecificMarkdownHelp() { return !this.isContentEditorActive; }, - displaySwitchBackToClassicEditorMessage() { - return ( - !this.glFeatures.wikiSwitchBetweenContentEditorRawMarkdown && this.isContentEditorActive - ); - }, disableSubmitButton() { return this.noContent || !this.title || this.contentEditorRenderFailed; }, @@ -312,23 +255,6 @@ export default { this.commitMessage = newCommitMessage; }, - initContentEditor() { - this.useContentEditor = true; - }, - - switchToOldEditor() { - this.useContentEditor = false; - }, - - confirmSwitchToOldEditor() { - if (this.contentEditorRenderFailed) { - this.contentEditorRenderFailed = false; - this.switchToOldEditor(); - } else { - this.$refs.confirmSwitchToOldEditorModal.show(); - } - }, - trackContentEditorLoaded() { this.track(CONTENT_EDITOR_LOADED_ACTION); }, @@ -349,10 +275,6 @@ export default { }, }); }, - - dismissContentEditorAlert() { - this.isContentEditorAlertDismissed = true; - }, }, }; </script> @@ -438,10 +360,7 @@ export default { }}</label> </div> <div class="col-sm-10"> - <div - v-if="showSwitchEditingModeButton" - class="gl-display-flex gl-justify-content-end gl-mb-3" - > + <div v-if="isMarkdownFormat" class="gl-display-flex gl-justify-content-end gl-mb-3"> <gl-button data-testid="toggle-editing-mode-button" data-qa-selector="editing_mode_button" @@ -451,42 +370,6 @@ export default { >{{ toggleEditingModeButtonText }}</gl-button > </div> - <gl-alert - v-if="showContentEditorAlert" - class="gl-mb-6" - variant="info" - data-qa-selector="try_new_editor_container" - :primary-button-text="$options.i18n.contentEditor.useNewEditor.primaryLabel" - :secondary-button-text="$options.i18n.contentEditor.useNewEditor.secondaryLabel" - :dismiss-label="$options.i18n.contentEditor.useNewEditor.secondaryLabel" - :title="$options.i18n.contentEditor.useNewEditor.title" - @primaryAction="initContentEditor" - @secondaryAction="dismissContentEditorAlert" - @dismiss="dismissContentEditorAlert" - > - <gl-sprintf :message="$options.i18n.contentEditor.useNewEditor.text"> - <template - #link="// eslint-disable-next-line vue/no-template-shadow - { content }" - ><gl-link - :href="contentEditorHelpPath" - target="_blank" - data-testid="content-editor-help-link" - >{{ content }}</gl-link - ></template - > - </gl-sprintf> - </gl-alert> - <gl-modal - ref="confirmSwitchToOldEditorModal" - modal-id="confirm-switch-to-old-editor" - :title="$options.i18n.contentEditor.switchToOldEditor.modal.title" - :action-primary="{ text: $options.i18n.contentEditor.switchToOldEditor.modal.primary }" - :action-cancel="{ text: $options.i18n.contentEditor.switchToOldEditor.modal.cancel }" - @primary="switchToOldEditor" - > - {{ $options.i18n.contentEditor.switchToOldEditor.modal.text }} - </gl-modal> <markdown-field v-if="!isContentEditorActive" :markdown-preview-path="pageInfo.markdownPreviewPath" @@ -516,22 +399,7 @@ export default { </textarea> </template> </markdown-field> - <div v-if="isContentEditorActive"> - <gl-alert class="gl-mb-6" variant="tip" :dismissible="false"> - <gl-sprintf :message="$options.i18n.contentEditor.feedbackTip"> - <template - #link="// eslint-disable-next-line vue/no-template-shadow - { content }" - ><gl-link - :href="$options.contentEditorFeedbackIssue" - target="_blank" - data-testid="wiki-markdown-help-link" - >{{ content }}</gl-link - ></template - > - </gl-sprintf> - </gl-alert> <content-editor :render-markdown="renderMarkdown" :uploads-path="pageInfo.uploadsPath" @@ -560,12 +428,6 @@ export default { ></template > </gl-sprintf> - <span v-if="displaySwitchBackToClassicEditorMessage"> - {{ $options.i18n.contentEditor.switchToOldEditor.helpText }} - <gl-button variant="link" @click="confirmSwitchToOldEditor">{{ - $options.i18n.contentEditor.switchToOldEditor.label - }}</gl-button> - </span> </div> </div> </div> diff --git a/app/assets/javascripts/pages/shared/wikis/edit.js b/app/assets/javascripts/pages/shared/wikis/edit.js index beeabfde1a6..02878633916 100644 --- a/app/assets/javascripts/pages/shared/wikis/edit.js +++ b/app/assets/javascripts/pages/shared/wikis/edit.js @@ -3,8 +3,8 @@ import Vue from 'vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import csrf from '~/lib/utils/csrf'; import Translate from '~/vue_shared/translate'; -import GLForm from '../../../gl_form'; -import ZenMode from '../../../zen_mode'; +import GLForm from '~/gl_form'; +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'; diff --git a/app/assets/javascripts/pages/shared/wikis/render_gfm_facade.js b/app/assets/javascripts/pages/shared/wikis/render_gfm_facade.js new file mode 100644 index 00000000000..90cc2983153 --- /dev/null +++ b/app/assets/javascripts/pages/shared/wikis/render_gfm_facade.js @@ -0,0 +1,5 @@ +import $ from 'jquery'; + +export const renderGFM = (el) => { + return $(el).renderGFM(); +}; diff --git a/app/assets/javascripts/pages/shared/wikis/show.js b/app/assets/javascripts/pages/shared/wikis/show.js new file mode 100644 index 00000000000..9906cb595f8 --- /dev/null +++ b/app/assets/javascripts/pages/shared/wikis/show.js @@ -0,0 +1,27 @@ +import Vue from 'vue'; +import Wikis from './wikis'; +import WikiContent from './components/wiki_content.vue'; + +const mountWikiContentApp = () => { + const el = document.querySelector('.js-async-wiki-page-content'); + + if (el) { + const { getWikiContentUrl } = el.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el, + render(createElement) { + return createElement(WikiContent, { + props: { getWikiContentUrl }, + }); + }, + }); + } +}; + +export const mountApplications = () => { + // eslint-disable-next-line no-new + new Wikis(); + mountWikiContentApp(); +}; |