diff options
Diffstat (limited to 'app/assets/javascripts')
18 files changed, 189 insertions, 26 deletions
diff --git a/app/assets/javascripts/admin/dev_ops_score/components/usage_ping_disabled.vue b/app/assets/javascripts/admin/dev_ops_score/components/usage_ping_disabled.vue new file mode 100644 index 00000000000..5429ec403d3 --- /dev/null +++ b/app/assets/javascripts/admin/dev_ops_score/components/usage_ping_disabled.vue @@ -0,0 +1,53 @@ +<script> +import { GlEmptyState, GlSprintf, GlLink, GlButton } from '@gitlab/ui'; + +export default { + components: { + GlEmptyState, + GlSprintf, + GlLink, + GlButton, + }, + inject: { + isAdmin: { + type: Boolean, + }, + svgPath: { + type: String, + }, + docsLink: { + type: String, + }, + primaryButtonPath: { + type: String, + }, + }, +}; +</script> +<template> + <gl-empty-state class="js-empty-state" :title="__('Usage ping is off')" :svg-path="svgPath"> + <template #description> + <gl-sprintf + v-if="!isAdmin" + :message=" + __( + 'To view instance-level analytics, ask an admin to turn on %{docLinkStart}usage ping%{docLinkEnd}.', + ) + " + > + <template #docLink="{content}"> + <gl-link :href="docsLink" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + <template v-else + ><p> + {{ __('Turn on usage ping to review instance-level analytics.') }} + </p> + + <gl-button category="primary" variant="success" :href="primaryButtonPath"> + {{ __('Turn on usage ping') }}</gl-button + > + </template> + </template> + </gl-empty-state> +</template> diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 66d7c2e3530..4ba9c29f9a9 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -468,7 +468,7 @@ export default { <div :data-can-create-note="getNoteableData.current_user.can_create_note" - class="files d-flex" + class="files d-flex gl-mt-2" > <div v-if="showTreeList" diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index e767c8cbefb..e3e140ea35e 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -133,6 +133,7 @@ export default { 'toggleFileDiscussions', 'toggleFileDiscussionWrappers', 'toggleFullDiff', + 'toggleActiveFileByHash', ]), handleToggleFile() { this.$emit('toggleFile'); @@ -149,6 +150,9 @@ export default { const selector = this.diffContentIDSelector; scrollToElement(document.querySelector(selector)); window.location.hash = selector; + if (!this.viewDiffsFileByFile) { + this.toggleActiveFileByHash(this.diffFile.file_hash); + } } }, }, diff --git a/app/assets/javascripts/diffs/components/diff_file_row.vue b/app/assets/javascripts/diffs/components/diff_file_row.vue index 43b669625f4..2856e6ae8eb 100644 --- a/app/assets/javascripts/diffs/components/diff_file_row.vue +++ b/app/assets/javascripts/diffs/components/diff_file_row.vue @@ -3,9 +3,10 @@ * This component is an iterative step towards refactoring and simplifying `vue_shared/components/file_row.vue` * https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23720 */ +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import FileRow from '~/vue_shared/components/file_row.vue'; -import FileRowStats from './file_row_stats.vue'; import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue'; +import FileRowStats from './file_row_stats.vue'; export default { name: 'DiffFileRow', @@ -14,6 +15,7 @@ export default { FileRowStats, ChangedFileIcon, }, + mixins: [glFeatureFlagsMixin()], props: { file: { type: Object, @@ -28,11 +30,28 @@ export default { required: false, default: null, }, + viewedFiles: { + type: Object, + required: false, + default: () => ({}), + }, }, computed: { showFileRowStats() { return !this.hideFileStats && this.file.type === 'blob'; }, + fileClasses() { + if (!this.glFeatures.highlightCurrentDiffRow) { + return ''; + } + + return this.file.type === 'blob' && !this.viewedFiles[this.file.fileHash] + ? 'gl-font-weight-bold' + : ''; + }, + isActive() { + return this.currentDiffFileId === this.file.fileHash; + }, }, }; </script> @@ -41,8 +60,9 @@ export default { <file-row :file="file" v-bind="$attrs" - :class="{ 'is-active': currentDiffFileId === file.fileHash }" + :class="{ 'is-active': isActive }" class="diff-file-row" + :file-classes="fileClasses" v-on="$listeners" > <file-row-stats v-if="showFileRowStats" :file="file" class="mr-1" /> diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue index 64c4715267f..d03d450b12d 100644 --- a/app/assets/javascripts/diffs/components/tree_list.vue +++ b/app/assets/javascripts/diffs/components/tree_list.vue @@ -25,7 +25,7 @@ export default { }; }, computed: { - ...mapState('diffs', ['tree', 'renderTreeList', 'currentDiffFileId']), + ...mapState('diffs', ['tree', 'renderTreeList', 'currentDiffFileId', 'viewedDiffFileIds']), ...mapGetters('diffs', ['allBlobs']), filteredTreeList() { const search = this.search.toLowerCase().trim(); @@ -93,6 +93,7 @@ export default { :key="file.key" :file="file" :level="0" + :viewed-files="viewedDiffFileIds" :hide-file-stats="hideFileStats" :file-row-component="$options.DiffFileRow" :current-diff-file-id="currentDiffFileId" diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js index 447136036ee..a79a385b9bc 100644 --- a/app/assets/javascripts/diffs/constants.js +++ b/app/assets/javascripts/diffs/constants.js @@ -34,7 +34,6 @@ export const COUNT_OF_AVATARS_IN_GUTTER = 3; export const LENGTH_OF_AVATAR_TOOLTIP = 17; export const LINES_TO_BE_RENDERED_DIRECTLY = 100; -export const MAX_LINES_TO_BE_RENDERED = 2000; export const DIFF_FILE_SYMLINK_MODE = '120000'; export const DIFF_FILE_DELETED_MODE = '0'; diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index e0470a7d93f..0f275f1cb3e 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -84,7 +84,7 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => { commit(types.SET_BATCH_LOADING, false); if (!isNoteLink && !state.currentDiffFileId) { - commit(types.UPDATE_CURRENT_DIFF_FILE_ID, diff_files[0].file_hash); + commit(types.VIEW_DIFF_FILE, diff_files[0].file_hash); } if (isNoteLink) { @@ -100,7 +100,7 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => { !state.diffFiles.some(f => f.file_hash === state.currentDiffFileId) && !isNoteLink ) { - commit(types.UPDATE_CURRENT_DIFF_FILE_ID, state.diffFiles[0].file_hash); + commit(types.VIEW_DIFF_FILE, state.diffFiles[0].file_hash); } if (gon.features?.codeNavigation) { @@ -183,7 +183,7 @@ export const fetchCoverageFiles = ({ commit, state }) => { export const setHighlightedRow = ({ commit }, lineCode) => { const fileHash = lineCode.split('_')[0]; commit(types.SET_HIGHLIGHTED_ROW, lineCode); - commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash); + commit(types.VIEW_DIFF_FILE, fileHash); }; // This is adding line discussions to the actual lines in the diff tree @@ -428,13 +428,17 @@ export const toggleTreeOpen = ({ commit }, path) => { commit(types.TOGGLE_FOLDER_OPEN, path); }; +export const toggleActiveFileByHash = ({ commit }, hash) => { + commit(types.VIEW_DIFF_FILE, hash); +}; + export const scrollToFile = ({ state, commit }, path) => { if (!state.treeEntries[path]) return; const { fileHash } = state.treeEntries[path]; document.location.hash = fileHash; - commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash); + commit(types.VIEW_DIFF_FILE, fileHash); }; export const toggleShowTreeList = ({ commit, state }, saving = true) => { @@ -702,7 +706,7 @@ export const setCurrentDiffFileIdFromNote = ({ commit, state, rootGetters }, not const fileHash = rootGetters.getDiscussion(note.discussion_id).diff_file?.file_hash; if (fileHash && state.diffFiles.some(f => f.file_hash === fileHash)) { - commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash); + commit(types.VIEW_DIFF_FILE, fileHash); } }; @@ -710,5 +714,5 @@ export const navigateToDiffFileIndex = ({ commit, state }, index) => { const fileHash = state.diffFiles[index].file_hash; document.location.hash = fileHash; - commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash); + commit(types.VIEW_DIFF_FILE, fileHash); }; diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js index c8f6f6bb0e5..001d9d9f594 100644 --- a/app/assets/javascripts/diffs/store/modules/diff_state.js +++ b/app/assets/javascripts/diffs/store/modules/diff_state.js @@ -34,6 +34,7 @@ export default () => ({ showTreeList: true, currentDiffFileId: '', projectPath: '', + viewedDiffFileIds: {}, commentForms: [], highlightedRow: null, renderTreeList: true, diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js index 4b1dbc34902..5dba2e9d10d 100644 --- a/app/assets/javascripts/diffs/store/mutation_types.js +++ b/app/assets/javascripts/diffs/store/mutation_types.js @@ -19,7 +19,7 @@ export const SET_LINE_DISCUSSIONS_FOR_FILE = 'SET_LINE_DISCUSSIONS_FOR_FILE'; export const REMOVE_LINE_DISCUSSIONS_FOR_FILE = 'REMOVE_LINE_DISCUSSIONS_FOR_FILE'; export const TOGGLE_FOLDER_OPEN = 'TOGGLE_FOLDER_OPEN'; export const TOGGLE_SHOW_TREE_LIST = 'TOGGLE_SHOW_TREE_LIST'; -export const UPDATE_CURRENT_DIFF_FILE_ID = 'UPDATE_CURRENT_DIFF_FILE_ID'; +export const VIEW_DIFF_FILE = 'VIEW_DIFF_FILE'; export const OPEN_DIFF_FILE_COMMENT_FORM = 'OPEN_DIFF_FILE_COMMENT_FORM'; export const UPDATE_DIFF_FILE_COMMENT_FORM = 'UPDATE_DIFF_FILE_COMMENT_FORM'; diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index c39532599cb..268326f9246 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -1,3 +1,4 @@ +import Vue from 'vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { PARALLEL_DIFF_VIEW_TYPE } from '../constants'; import { @@ -291,8 +292,9 @@ export default { [types.TOGGLE_SHOW_TREE_LIST](state) { state.showTreeList = !state.showTreeList; }, - [types.UPDATE_CURRENT_DIFF_FILE_ID](state, fileId) { + [types.VIEW_DIFF_FILE](state, fileId) { state.currentDiffFileId = fileId; + Vue.set(state.viewedDiffFileIds, fileId, true); }, [types.OPEN_DIFF_FILE_COMMENT_FORM](state, formData) { state.commentForms.push({ diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index 320b44bd537..3bdb79d3899 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -11,7 +11,6 @@ import { OLD_LINE_TYPE, MATCH_LINE_TYPE, LINES_TO_BE_RENDERED_DIRECTLY, - MAX_LINES_TO_BE_RENDERED, TREE_TYPE, INLINE_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE, @@ -457,12 +456,10 @@ function getVisibleDiffLines(file) { } function finalizeDiffFile(file) { - const name = (file.viewer && file.viewer.name) || diffViewerModes.text; const lines = getVisibleDiffLines(file); Object.assign(file, { renderIt: lines < LINES_TO_BE_RENDERED_DIRECTLY, - collapsed: name === diffViewerModes.text && lines > MAX_LINES_TO_BE_RENDERED, isShowingFullFile: false, isLoadingFullFile: false, discussions: [], diff --git a/app/assets/javascripts/milestones/project_milestone_combobox.vue b/app/assets/javascripts/milestones/project_milestone_combobox.vue index 4e61e8b4262..b2196bd9ceb 100644 --- a/app/assets/javascripts/milestones/project_milestone_combobox.vue +++ b/app/assets/javascripts/milestones/project_milestone_combobox.vue @@ -89,6 +89,14 @@ export default { return this.requestCount !== 0; }, }, + created() { + // This method is defined here instead of in `methods` + // because we need to access the .cancel() method + // lodash attaches to the function, which is + // made inaccessible by Vue. More info: + // https://stackoverflow.com/a/52988020/1063392 + this.debouncedSearchMilestones = debounce(this.searchMilestones, 100); + }, mounted() { this.fetchMilestones(); }, @@ -108,7 +116,7 @@ export default { this.requestCount -= 1; }); }, - searchMilestones: debounce(function searchMilestones() { + searchMilestones() { this.requestCount += 1; const options = { search: this.searchQuery, @@ -133,7 +141,14 @@ export default { .finally(() => { this.requestCount -= 1; }); - }, 100), + }, + onSearchBoxInput() { + this.debouncedSearchMilestones(); + }, + onSearchBoxEnter() { + this.debouncedSearchMilestones.cancel(); + this.searchMilestones(); + }, toggleMilestoneSelection(clickedMilestone) { if (!clickedMilestone) return []; @@ -186,7 +201,8 @@ export default { v-model.trim="searchQuery" class="gl-m-3" :placeholder="this.$options.translations.searchMilestones" - @input="searchMilestones" + @input="onSearchBoxInput" + @keydown.enter.prevent="onSearchBoxEnter" /> <gl-new-dropdown-item @click="onMilestoneClicked(null)"> diff --git a/app/assets/javascripts/pages/admin/dev_ops_score/index.js b/app/assets/javascripts/pages/admin/dev_ops_score/index.js index c1056537f90..9d018408b88 100644 --- a/app/assets/javascripts/pages/admin/dev_ops_score/index.js +++ b/app/assets/javascripts/pages/admin/dev_ops_score/index.js @@ -1,3 +1,27 @@ +import Vue from 'vue'; import UserCallout from '~/user_callout'; +import UsagePingDisabled from '~/admin/dev_ops_score/components/usage_ping_disabled.vue'; -document.addEventListener('DOMContentLoaded', () => new UserCallout()); +document.addEventListener('DOMContentLoaded', () => { + // eslint-disable-next-line no-new + new UserCallout(); + + const emptyStateContainer = document.getElementById('js-devops-empty-state'); + + if (!emptyStateContainer) return false; + + const { emptyStateSvgPath, enableUsagePingLink, docsLink, isAdmin } = emptyStateContainer.dataset; + + return new Vue({ + el: emptyStateContainer, + provide: { + isAdmin: Boolean(isAdmin), + svgPath: emptyStateSvgPath, + primaryButtonPath: enableUsagePingLink, + docsLink, + }, + render(h) { + return h(UsagePingDisabled); + }, + }); +}); diff --git a/app/assets/javascripts/ref/components/ref_selector.vue b/app/assets/javascripts/ref/components/ref_selector.vue index e388604ed92..dd84246159b 100644 --- a/app/assets/javascripts/ref/components/ref_selector.vue +++ b/app/assets/javascripts/ref/components/ref_selector.vue @@ -87,6 +87,15 @@ export default { }, }, created() { + // This method is defined here instead of in `methods` + // because we need to access the .cancel() method + // lodash attaches to the function, which is + // made inaccessible by Vue. More info: + // https://stackoverflow.com/a/52988020/1063392 + this.debouncedSearch = debounce(function search() { + this.search(this.query); + }, SEARCH_DEBOUNCE_MS); + this.setProjectId(this.projectId); this.search(this.query); }, @@ -95,9 +104,13 @@ export default { focusSearchBox() { this.$refs.searchBox.$el.querySelector('input').focus(); }, - onSearchBoxInput: debounce(function search() { + onSearchBoxEnter() { + this.debouncedSearch.cancel(); this.search(this.query); - }, SEARCH_DEBOUNCE_MS), + }, + onSearchBoxInput() { + this.debouncedSearch(); + }, selectRef(ref) { this.setSelectedRef(ref); this.$emit('input', this.selectedRef); @@ -129,6 +142,7 @@ export default { class="gl-m-3" :placeholder="i18n.searchPlaceholder" @input="onSearchBoxInput" + @keydown.enter.prevent="onSearchBoxEnter" /> <div class="gl-flex-grow-1 gl-overflow-y-auto"> diff --git a/app/assets/javascripts/releases/components/app_edit_new.vue b/app/assets/javascripts/releases/components/app_edit_new.vue index e34be1bd871..e1edf3d689d 100644 --- a/app/assets/javascripts/releases/components/app_edit_new.vue +++ b/app/assets/javascripts/releases/components/app_edit_new.vue @@ -140,7 +140,7 @@ export default { class="form-control" /> </gl-form-group> - <gl-form-group class="w-50" data-testid="milestones-field" @keydown.enter.prevent.capture> + <gl-form-group class="w-50" data-testid="milestones-field"> <label>{{ __('Milestones') }}</label> <div class="d-flex flex-column col-md-6 col-sm-10 pl-0"> <milestone-combobox diff --git a/app/assets/javascripts/static_site_editor/services/parse_source_file.js b/app/assets/javascripts/static_site_editor/services/parse_source_file.js index dca34e16e8d..57505d50d6e 100644 --- a/app/assets/javascripts/static_site_editor/services/parse_source_file.js +++ b/app/assets/javascripts/static_site_editor/services/parse_source_file.js @@ -1,6 +1,11 @@ -const parseSourceFile = raw => { - const frontMatterRegex = /(^---$[\s\S]*?^---$)/m; - const preGroupedRegex = /([\s\S]*?)(^---$[\s\S]*?^---$)(\s*)([\s\S]*)/m; // preFrontMatter, frontMatter, spacing, and content +import getFrontMatterLanguageDefinition from './parse_source_file_language_support'; + +const parseSourceFile = (raw, options = { frontMatterLanguage: 'yaml' }) => { + const { open, close } = getFrontMatterLanguageDefinition(options.frontMatterLanguage); + const anyChar = '[\\s\\S]'; + const frontMatterBlock = `^${open}$${anyChar}*?^${close}$`; + const frontMatterRegex = new RegExp(`${frontMatterBlock}`, 'm'); + const preGroupedRegex = new RegExp(`(${anyChar}*?)(${frontMatterBlock})(\\s*)(${anyChar}*)`, 'm'); // preFrontMatter, frontMatter, spacing, and content let initial; let editable; diff --git a/app/assets/javascripts/static_site_editor/services/parse_source_file_language_support.js b/app/assets/javascripts/static_site_editor/services/parse_source_file_language_support.js new file mode 100644 index 00000000000..ec0eaca81b8 --- /dev/null +++ b/app/assets/javascripts/static_site_editor/services/parse_source_file_language_support.js @@ -0,0 +1,17 @@ +const frontMatterLanguageDefinitions = [ + { name: 'yaml', open: '---', close: '---' }, + { name: 'toml', open: '\\+\\+\\+', close: '\\+\\+\\+' }, + { name: 'json', open: '{', close: '}' }, +]; + +const getFrontMatterLanguageDefinition = name => { + const languageDefinition = frontMatterLanguageDefinitions.find(def => def.name === name); + + if (!languageDefinition) { + throw new Error(`Unsupported front matter language: ${name}`); + } + + return languageDefinition; +}; + +export default getFrontMatterLanguageDefinition; diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue index 0952e37e46e..004bf882dd6 100644 --- a/app/assets/javascripts/vue_shared/components/file_row.vue +++ b/app/assets/javascripts/vue_shared/components/file_row.vue @@ -18,6 +18,11 @@ export default { type: Number, required: true, }, + fileClasses: { + type: String, + required: false, + default: '', + }, }, computed: { isTree() { @@ -123,6 +128,7 @@ export default { :style="levelIndentation" class="file-row-name str-truncated" data-qa-selector="file_name_content" + :class="fileClasses" > <file-icon class="file-row-icon" |