diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-14 18:09:57 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-14 18:09:57 +0300 |
commit | b689f371350fbf1b71f266764ee018befc9b91f7 (patch) | |
tree | 7de1d3ab26d3cae0ac2a7a8ccd8302fcdaac5534 /app | |
parent | 0b194c4854f312e36616fccf7c610cb2b0ec6957 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
21 files changed, 400 insertions, 160 deletions
diff --git a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue index d58033b36c7..a490111e13b 100644 --- a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue +++ b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue @@ -62,6 +62,7 @@ export default { projects: [], selectedProjects: this.defaultProjects || [], searchTerm: '', + isDirty: false, }; }, computed: { @@ -124,6 +125,24 @@ export default { this.setSelectedProjects(project, !isSelected); this.$emit('selected', this.selectedProjects); }, + onMultiSelectClick({ project, isSelected }) { + this.setSelectedProjects(project, !isSelected); + this.isDirty = true; + }, + onSelected(ev) { + if (this.multiSelect) { + this.onMultiSelectClick(ev); + } else { + this.onClick(ev); + } + }, + onHide() { + if (this.multiSelect && this.isDirty) { + this.$emit('selected', this.selectedProjects); + } + this.searchTerm = ''; + this.isDirty = false; + }, fetchData() { this.loading = true; @@ -158,12 +177,12 @@ export default { }, }; </script> - <template> <gl-dropdown ref="projectsDropdown" class="dropdown dropdown-projects" toggle-class="gl-shadow-none" + @hide="onHide" > <template #button-content> <div class="gl-display-flex gl-flex-grow-1"> @@ -181,15 +200,18 @@ export default { </div> <gl-icon class="gl-ml-2" name="chevron-down" /> </template> - <gl-dropdown-section-header>{{ __('Projects') }}</gl-dropdown-section-header> - <gl-search-box-by-type v-model.trim="searchTerm" /> - + <template #header> + <gl-dropdown-section-header>{{ __('Projects') }}</gl-dropdown-section-header> + <gl-search-box-by-type v-model.trim="searchTerm" /> + </template> <gl-dropdown-item v-for="project in availableProjects" :key="project.id" :is-check-item="true" :is-checked="isProjectSelected(project.id)" - @click.prevent="onClick({ project, isSelected: isProjectSelected(project.id) })" + @click.native.capture.stop=" + onSelected({ project, isSelected: isProjectSelected(project.id) }) + " > <div class="gl-display-flex"> <gl-avatar @@ -203,7 +225,9 @@ export default { /> <div> <div data-testid="project-name">{{ project.name }}</div> - <div class="gl-text-gray-500" data-testid="project-full-path">{{ project.fullPath }}</div> + <div class="gl-text-gray-500" data-testid="project-full-path"> + {{ project.fullPath }} + </div> </div> </div> </gl-dropdown-item> diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js index 6cd0a62657e..a32a100fa11 100644 --- a/app/assets/javascripts/boards/stores/mutations.js +++ b/app/assets/javascripts/boards/stores/mutations.js @@ -35,13 +35,23 @@ export const addItemToList = ({ state, listId, itemId, moveBeforeId, moveAfterId export default { [mutationTypes.SET_INITIAL_BOARD_DATA](state, data) { - const { boardType, disabled, boardId, fullBoardId, fullPath, boardConfig, issuableType } = data; + const { + allowSubEpics, + boardConfig, + boardId, + boardType, + disabled, + fullBoardId, + fullPath, + issuableType, + } = data; + state.allowSubEpics = allowSubEpics; + state.boardConfig = boardConfig; state.boardId = boardId; - state.fullBoardId = fullBoardId; - state.fullPath = fullPath; state.boardType = boardType; state.disabled = disabled; - state.boardConfig = boardConfig; + state.fullBoardId = fullBoardId; + state.fullPath = fullPath; state.issuableType = issuableType; }, diff --git a/app/assets/javascripts/branches/components/delete_branch_button.vue b/app/assets/javascripts/branches/components/delete_branch_button.vue index 5a5f49e25e7..6a6d4d48c52 100644 --- a/app/assets/javascripts/branches/components/delete_branch_button.vue +++ b/app/assets/javascripts/branches/components/delete_branch_button.vue @@ -47,12 +47,6 @@ export default { }, }, computed: { - variant() { - if (this.disabled) { - return 'default'; - } - return 'danger'; - }, title() { if (this.isProtectedBranch && this.disabled) { return s__('Branches|Only a project maintainer or owner can delete a protected branch'); @@ -83,7 +77,7 @@ export default { class="js-delete-branch-button" data-qa-selector="delete_branch_button" :disabled="disabled" - :variant="variant" + variant="default" :title="title" :aria-label="title" @click="openModal" diff --git a/app/assets/javascripts/content_editor/components/toolbar_image_button.vue b/app/assets/javascripts/content_editor/components/toolbar_image_button.vue new file mode 100644 index 00000000000..ebeee16dbec --- /dev/null +++ b/app/assets/javascripts/content_editor/components/toolbar_image_button.vue @@ -0,0 +1,110 @@ +<script> +import { + GlDropdown, + GlDropdownForm, + GlButton, + GlFormInputGroup, + GlDropdownDivider, + GlDropdownItem, + GlTooltipDirective as GlTooltip, +} from '@gitlab/ui'; +import { Editor as TiptapEditor } from '@tiptap/vue-2'; +import { acceptedMimes } from '../extensions/image'; +import { getImageAlt } from '../services/utils'; + +export default { + components: { + GlDropdown, + GlDropdownForm, + GlFormInputGroup, + GlDropdownDivider, + GlDropdownItem, + GlButton, + }, + directives: { + GlTooltip, + }, + props: { + tiptapEditor: { + type: TiptapEditor, + required: true, + }, + }, + data() { + return { + imgSrc: '', + }; + }, + methods: { + resetFields() { + this.imgSrc = ''; + this.$refs.fileSelector.value = ''; + }, + insertImage() { + this.tiptapEditor + .chain() + .focus() + .setImage({ + src: this.imgSrc, + canonicalSrc: this.imgSrc, + alt: getImageAlt(this.imgSrc), + }) + .run(); + + this.resetFields(); + this.emitExecute(); + }, + emitExecute(source = 'url') { + this.$emit('execute', { contentType: 'image', value: source }); + }, + openFileUpload() { + this.$refs.fileSelector.click(); + }, + onFileSelect(e) { + this.tiptapEditor + .chain() + .focus() + .uploadImage({ + file: e.target.files[0], + }) + .run(); + + this.resetFields(); + this.emitExecute('upload'); + }, + }, + acceptedMimes, +}; +</script> +<template> + <gl-dropdown + v-gl-tooltip + :aria-label="__('Insert image')" + :title="__('Insert image')" + size="small" + category="tertiary" + icon="media" + @hidden="resetFields()" + > + <gl-dropdown-form class="gl-px-3!"> + <gl-form-input-group v-model="imgSrc" :placeholder="__('Image URL')"> + <template #append> + <gl-button variant="confirm" @click="insertImage">{{ __('Insert') }}</gl-button> + </template> + </gl-form-input-group> + </gl-dropdown-form> + <gl-dropdown-divider /> + <gl-dropdown-item @click="openFileUpload"> + {{ __('Upload image') }} + </gl-dropdown-item> + + <input + ref="fileSelector" + type="file" + name="content_editor_image" + :accept="$options.acceptedMimes" + class="gl-display-none" + @change="onFileSelect" + /> + </gl-dropdown> +</template> diff --git a/app/assets/javascripts/content_editor/components/toolbar_link_button.vue b/app/assets/javascripts/content_editor/components/toolbar_link_button.vue index c0ced2d2228..8f57959a73f 100644 --- a/app/assets/javascripts/content_editor/components/toolbar_link_button.vue +++ b/app/assets/javascripts/content_editor/components/toolbar_link_button.vue @@ -43,7 +43,7 @@ export default { }, mounted() { this.tiptapEditor.on('selectionUpdate', ({ editor }) => { - const { 'data-canonical-src': canonicalSrc, href } = editor.getAttributes(linkContentType); + const { canonicalSrc, href } = editor.getAttributes(linkContentType); this.linkHref = canonicalSrc || href; }); @@ -56,7 +56,7 @@ export default { .unsetLink() .setLink({ href: this.linkHref, - 'data-canonical-src': this.linkHref, + canonicalSrc: this.linkHref, }) .run(); diff --git a/app/assets/javascripts/content_editor/components/top_toolbar.vue b/app/assets/javascripts/content_editor/components/top_toolbar.vue index e1bc26d50fc..fafc7a660e7 100644 --- a/app/assets/javascripts/content_editor/components/top_toolbar.vue +++ b/app/assets/javascripts/content_editor/components/top_toolbar.vue @@ -4,6 +4,7 @@ import { CONTENT_EDITOR_TRACKING_LABEL, TOOLBAR_CONTROL_TRACKING_ACTION } from ' import { ContentEditor } from '../services/content_editor'; import Divider from './divider.vue'; import ToolbarButton from './toolbar_button.vue'; +import ToolbarImageButton from './toolbar_image_button.vue'; import ToolbarLinkButton from './toolbar_link_button.vue'; import ToolbarTableButton from './toolbar_table_button.vue'; import ToolbarTextStyleDropdown from './toolbar_text_style_dropdown.vue'; @@ -18,6 +19,7 @@ export default { ToolbarTextStyleDropdown, ToolbarLinkButton, ToolbarTableButton, + ToolbarImageButton, Divider, }, mixins: [trackingMixin], @@ -89,6 +91,12 @@ export default { @execute="trackToolbarControlExecution" /> <divider /> + <toolbar-image-button + ref="imageButton" + data-testid="image" + :tiptap-editor="contentEditor.tiptapEditor" + @execute="trackToolbarControlExecution" + /> <toolbar-button data-testid="blockquote" content-type="blockquote" @@ -140,3 +148,8 @@ export default { /> </div> </template> +<style> +.gl-spinner-container { + text-align: left; +} +</style> diff --git a/app/assets/javascripts/content_editor/extensions/link.js b/app/assets/javascripts/content_editor/extensions/link.js index 89076e0210c..12019ab4636 100644 --- a/app/assets/javascripts/content_editor/extensions/link.js +++ b/app/assets/javascripts/content_editor/extensions/link.js @@ -38,11 +38,11 @@ export const tiptapExtension = Link.extend({ }; }, }, - 'data-canonical-src': { + canonicalSrc: { default: null, parseHTML: (element) => { return { - href: element.dataset.canonicalSrc, + canonicalSrc: element.dataset.canonicalSrc, }; }, }, @@ -57,7 +57,7 @@ export const serializer = { return '['; }, close(state, mark) { - const href = mark.attrs['data-canonical-src'] || mark.attrs.href; + const href = mark.attrs.canonicalSrc || mark.attrs.href; return `](${state.esc(href)}${mark.attrs.title ? ` ${state.quote(mark.attrs.title)}` : ''})`; }, }; diff --git a/app/assets/javascripts/import_entities/components/group_dropdown.vue b/app/assets/javascripts/import_entities/components/group_dropdown.vue new file mode 100644 index 00000000000..44d6d17232f --- /dev/null +++ b/app/assets/javascripts/import_entities/components/group_dropdown.vue @@ -0,0 +1,40 @@ +<script> +import { GlDropdown, GlSearchBoxByType } from '@gitlab/ui'; + +export default { + components: { + GlDropdown, + GlSearchBoxByType, + }, + inheritAttrs: false, + props: { + namespaces: { + type: Array, + required: true, + }, + }, + data() { + return { searchTerm: '' }; + }, + computed: { + filteredNamespaces() { + return this.namespaces.filter((ns) => + ns.toLowerCase().includes(this.searchTerm.toLowerCase()), + ); + }, + }, +}; +</script> +<template> + <gl-dropdown + toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!" + class="import-entities-namespace-dropdown gl-h-7 gl-flex-fill-1" + data-qa-selector="target_namespace_selector_dropdown" + v-bind="$attrs" + > + <template #header> + <gl-search-box-by-type v-model.trim="searchTerm" /> + </template> + <slot :namespaces="filteredNamespaces"></slot> + </gl-dropdown> +</template> diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue index 63c18f4d78e..1c3ede769e0 100644 --- a/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue +++ b/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue @@ -1,7 +1,6 @@ <script> import { GlButton, - GlDropdown, GlDropdownDivider, GlDropdownItem, GlDropdownSectionHeader, @@ -11,6 +10,7 @@ import { } from '@gitlab/ui'; import { joinPaths } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; +import ImportGroupDropdown from '../../components/group_dropdown.vue'; import ImportStatus from '../../components/import_status.vue'; import { STATUSES } from '../../constants'; import addValidationErrorMutation from '../graphql/mutations/add_validation_error.mutation.graphql'; @@ -22,8 +22,8 @@ const DEBOUNCE_INTERVAL = 300; export default { components: { ImportStatus, + ImportGroupDropdown, GlButton, - GlDropdown, GlDropdownDivider, GlDropdownItem, GlDropdownSectionHeader, @@ -83,6 +83,10 @@ export default { }, computed: { + availableNamespaceNames() { + return this.availableNamespaces.map((ns) => ns.full_path); + }, + importTarget() { return this.group.import_target; }, @@ -153,9 +157,11 @@ export default { disabled: isAlreadyImported, }" > - <gl-dropdown + <import-group-dropdown + #default="{ namespaces }" :text="importTarget.target_namespace" :disabled="isAlreadyImported" + :namespaces="availableNamespaceNames" toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!" class="import-entities-namespace-dropdown gl-h-7 gl-flex-grow-1" data-qa-selector="target_namespace_selector_dropdown" @@ -163,22 +169,22 @@ export default { <gl-dropdown-item @click="$emit('update-target-namespace', '')">{{ s__('BulkImport|No parent') }}</gl-dropdown-item> - <template v-if="availableNamespaces.length"> + <template v-if="namespaces.length"> <gl-dropdown-divider /> <gl-dropdown-section-header> {{ s__('BulkImport|Existing groups') }} </gl-dropdown-section-header> <gl-dropdown-item - v-for="ns in availableNamespaces" - :key="ns.full_path" + v-for="ns in namespaces" + :key="ns" data-qa-selector="target_group_dropdown_item" - :data-qa-group-name="ns.full_path" - @click="$emit('update-target-namespace', ns.full_path)" + :data-qa-group-name="ns" + @click="$emit('update-target-namespace', ns)" > - {{ ns.full_path }} + {{ ns }} </gl-dropdown-item> </template> - </gl-dropdown> + </import-group-dropdown> <div class="import-entities-target-select-separator gl-h-7 gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1" > diff --git a/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue index be09052fb7e..14d08caef34 100644 --- a/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue +++ b/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue @@ -47,18 +47,7 @@ export default { }, availableNamespaces() { - const serializedNamespaces = this.namespaces.map(({ fullPath }) => ({ - id: fullPath, - text: fullPath, - })); - - return [ - { text: __('Groups'), children: serializedNamespaces }, - { - text: __('Users'), - children: [{ id: this.defaultTargetNamespace, text: this.defaultTargetNamespace }], - }, - ]; + return this.namespaces.map(({ fullPath }) => fullPath); }, importAllButtonText() { @@ -179,6 +168,7 @@ export default { :key="repo.importSource.providerLink" :repo="repo" :available-namespaces="availableNamespaces" + :user-namespace="defaultTargetNamespace" /> </template> </tbody> diff --git a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue index a803afeb901..e2fd608d9db 100644 --- a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue +++ b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue @@ -1,8 +1,17 @@ <script> -import { GlIcon, GlBadge, GlFormInput, GlButton, GlLink } from '@gitlab/ui'; +import { + GlIcon, + GlBadge, + GlFormInput, + GlButton, + GlLink, + GlDropdownItem, + GlDropdownDivider, + GlDropdownSectionHeader, +} from '@gitlab/ui'; import { mapState, mapGetters, mapActions } from 'vuex'; import { __ } from '~/locale'; -import Select2Select from '~/vue_shared/components/select2_select.vue'; +import ImportGroupDropdown from '../../components/group_dropdown.vue'; import ImportStatus from '../../components/import_status.vue'; import { STATUSES } from '../../constants'; import { isProjectImportable, isIncompatible, getImportStatus } from '../utils'; @@ -10,10 +19,13 @@ import { isProjectImportable, isIncompatible, getImportStatus } from '../utils'; export default { name: 'ProviderRepoTableRow', components: { - Select2Select, + ImportGroupDropdown, ImportStatus, GlFormInput, GlButton, + GlDropdownItem, + GlDropdownDivider, + GlDropdownSectionHeader, GlIcon, GlBadge, GlLink, @@ -23,6 +35,10 @@ export default { type: Object, required: true, }, + userNamespace: { + type: String, + required: true, + }, availableNamespaces: { type: Array, required: true, @@ -61,22 +77,6 @@ export default { return this.ciCdOnly ? __('Connect') : __('Import'); }, - select2Options() { - return { - data: this.availableNamespaces, - containerCssClass: 'import-namespace-select qa-project-namespace-select gl-w-auto', - }; - }, - - targetNamespaceSelect: { - get() { - return this.importTarget.targetNamespace; - }, - set(value) { - this.updateImportTarget({ targetNamespace: value }); - }, - }, - newNameInput: { get() { return this.importTarget.newName; @@ -118,7 +118,29 @@ export default { <template v-if="repo.importSource.target">{{ repo.importSource.target }}</template> <template v-else-if="isImportNotStarted"> <div class="import-entities-target-select gl-display-flex gl-align-items-stretch gl-w-full"> - <select2-select v-model="targetNamespaceSelect" :options="select2Options" /> + <import-group-dropdown + #default="{ namespaces }" + :text="importTarget.targetNamespace" + :namespaces="availableNamespaces" + > + <template v-if="namespaces.length"> + <gl-dropdown-section-header>{{ __('Groups') }}</gl-dropdown-section-header> + <gl-dropdown-item + v-for="ns in namespaces" + :key="ns" + data-qa-selector="target_group_dropdown_item" + :data-qa-group-name="ns" + @click="updateImportTarget({ targetNamespace: ns })" + > + {{ ns }} + </gl-dropdown-item> + <gl-dropdown-divider /> + </template> + <gl-dropdown-section-header>{{ __('Users') }}</gl-dropdown-section-header> + <gl-dropdown-item @click="updateImportTarget({ targetNamespace: ns })">{{ + userNamespace + }}</gl-dropdown-item> + </import-group-dropdown> <div class="import-entities-target-select-separator gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1" > diff --git a/app/assets/javascripts/import_entities/import_projects/index.js b/app/assets/javascripts/import_entities/import_projects/index.js index 6b7fe23ed60..110cc77b20d 100644 --- a/app/assets/javascripts/import_entities/import_projects/index.js +++ b/app/assets/javascripts/import_entities/import_projects/index.js @@ -38,7 +38,7 @@ export function initStoreFromElement(element) { export function initPropsFromElement(element) { return { - providerTitle: element.dataset.providerTitle, + providerTitle: element.dataset.provider, filterable: parseBoolean(element.dataset.filterable), paginatable: parseBoolean(element.dataset.paginatable), }; diff --git a/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue index 853e839a7ab..73c91839f04 100644 --- a/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue +++ b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue @@ -1,18 +1,27 @@ <script> -import { GlIcon } from '@gitlab/ui'; +import { GlAlert, GlLink, GlSprintf, GlIcon } from '@gitlab/ui'; import { uniqueId } from 'lodash'; +import { helpPagePath } from '~/helpers/help_page_helper'; import { s__ } from '~/locale'; import SourceEditor from '~/vue_shared/components/source_editor.vue'; +import getCurrentBranch from '../../graphql/queries/client/current_branch.graphql'; export default { i18n: { viewOnlyMessage: s__('Pipelines|Merged YAML is view only'), + unavailableDefaultTitle: s__('Pipelines|Merged YAML unavailable'), + unavailableDefaultText: s__( + 'Pipelines|The merged YAML view is only available for the default branch. %{linkStart}Learn more.%{linkEnd}', + ), }, components: { SourceEditor, + GlAlert, GlIcon, + GlLink, + GlSprintf, }, - inject: ['ciConfigPath'], + inject: ['ciConfigPath', 'defaultBranch'], props: { ciConfigData: { type: Object, @@ -24,6 +33,15 @@ export default { failureType: null, }; }, + // This is not the best practice, don't copy me (@samdbeckham) + // This is a temporary workaround to unblock a release. + // See this comment for more information on this approach + // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65972#note_626095648 + apollo: { + currentBranch: { + query: getCurrentBranch, + }, + }, computed: { fileGlobalId() { return `${this.ciConfigPath}-${uniqueId()}`; @@ -31,24 +49,44 @@ export default { mergedYaml() { return this.ciConfigData.mergedYaml; }, + isOnDefaultBranch() { + return this.currentBranch === this.defaultBranch; + }, + expandedConfigHelpPath() { + return helpPagePath('ci/pipeline_editor/index', { anchor: 'view-expanded-configuration' }); + }, }, }; </script> <template> <div> - <div class="gl-display-flex gl-align-items-center"> - <gl-icon :size="16" name="lock" class="gl-text-gray-500 gl-mr-3" /> - {{ $options.i18n.viewOnlyMessage }} - </div> - <div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1"> - <source-editor - ref="editor" - :value="mergedYaml" - :file-name="ciConfigPath" - :file-global-id="fileGlobalId" - :editor-options="{ readOnly: true }" - v-on="$listeners" - /> + <div v-if="isOnDefaultBranch"> + <div class="gl-display-flex gl-align-items-center"> + <gl-icon :size="16" name="lock" class="gl-text-gray-500 gl-mr-3" /> + {{ $options.i18n.viewOnlyMessage }} + </div> + <div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1"> + <source-editor + ref="editor" + :value="mergedYaml" + :file-name="ciConfigPath" + :file-global-id="fileGlobalId" + :editor-options="{ readOnly: true }" + v-on="$listeners" + /> + </div> </div> + <gl-alert + v-else + variant="info" + :dismissible="false" + :title="$options.i18n.unavailableDefaultTitle" + > + <gl-sprintf :message="$options.i18n.unavailableDefaultText"> + <template #link="{ content }"> + <gl-link :href="expandedConfigHelpPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </gl-alert> </div> </template> diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue index cf1cff9023e..c861fb8dd06 100644 --- a/app/assets/javascripts/repository/components/tree_content.vue +++ b/app/assets/javascripts/repository/components/tree_content.vue @@ -1,6 +1,7 @@ <script> import filesQuery from 'shared_queries/repository/files.query.graphql'; import createFlash from '~/flash'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { __ } from '../../locale'; import { TREE_PAGE_SIZE, TREE_INITIAL_FETCH_COUNT, TREE_PAGE_LIMIT } from '../constants'; import getRefMixin from '../mixins/get_ref'; @@ -14,7 +15,7 @@ export default { FileTable, FilePreview, }, - mixins: [getRefMixin], + mixins: [getRefMixin, glFeatureFlagMixin()], apollo: { projectPath: { query: projectPathQuery, @@ -52,7 +53,9 @@ export default { pageSize() { // we want to exponentially increase the page size to reduce the load on the frontend const exponentialSize = (TREE_PAGE_SIZE / TREE_INITIAL_FETCH_COUNT) * (this.fetchCounter + 1); - return exponentialSize < TREE_PAGE_SIZE ? exponentialSize : TREE_PAGE_SIZE; + return exponentialSize < TREE_PAGE_SIZE && this.glFeatures.increasePageSizeExponentially + ? exponentialSize + : TREE_PAGE_SIZE; }, totalEntries() { return Object.values(this.entries).flat().length; diff --git a/app/assets/javascripts/vue_shared/components/select2_select.vue b/app/assets/javascripts/vue_shared/components/select2_select.vue deleted file mode 100644 index bb1a8fae7b0..00000000000 --- a/app/assets/javascripts/vue_shared/components/select2_select.vue +++ /dev/null @@ -1,48 +0,0 @@ -<script> -import $ from 'jquery'; -import 'select2'; -import { loadCSSFile } from '~/lib/utils/css_utils'; - -export default { - // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26 - // eslint-disable-next-line @gitlab/require-i18n-strings - name: 'Select2Select', - props: { - options: { - type: Object, - required: false, - default: () => ({}), - }, - value: { - type: String, - required: false, - default: '', - }, - }, - - watch: { - value() { - $(this.$refs.dropdownInput).val(this.value).trigger('change'); - }, - }, - - mounted() { - loadCSSFile(gon.select2_css_path) - .then(() => { - $(this.$refs.dropdownInput) - .val(this.value) - .select2(this.options) - .on('change', (event) => this.$emit('input', event.target.value)); - }) - .catch(() => {}); - }, - - beforeDestroy() { - $(this.$refs.dropdownInput).select2('destroy'); - }, -}; -</script> - -<template> - <input ref="dropdownInput" type="hidden" /> -</template> diff --git a/app/assets/stylesheets/snippets.scss b/app/assets/stylesheets/snippets.scss index ad040f65f3c..d38c1818f53 100644 --- a/app/assets/stylesheets/snippets.scss +++ b/app/assets/stylesheets/snippets.scss @@ -54,6 +54,8 @@ white-space: pre; word-wrap: normal; border-left: $border-style; + text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; /* stylelint-disable-line property-no-vendor-prefix */ } code { @@ -65,7 +67,7 @@ } .line-numbers { - padding: 10px; + padding: 10px 10px 10px 0; text-align: right; float: left; @@ -86,18 +88,24 @@ } } + .file-actions { + flex-shrink: 0; + } + .file-title-flex-parent { display: flex; - align-items: center; + align-items: flex-start; justify-content: space-between; background-color: $gray-light; border: $border-style; border-bottom: 0; - padding: $gl-padding-top $gl-padding; + padding: $gl-padding; margin: 0; border-radius: $border-radius-default $border-radius-default 0 0; .file-header-content { + max-width: 75%; + .file-title-name { font-weight: $gl-font-weight-bold; } @@ -105,6 +113,7 @@ .gitlab-embedded-snippets-title { text-decoration: none; color: $gl-text-color; + word-break: break-word; &:hover { text-decoration: underline; diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 48d943587d4..0dbf7d40f87 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -37,6 +37,7 @@ class ProjectsController < Projects::ApplicationController before_action do push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml) + push_frontend_feature_flag(:increase_page_size_exponentially, @project, default_enabled: :yaml) end layout :determine_layout diff --git a/app/models/integrations/datadog.rb b/app/models/integrations/datadog.rb index 211c80f8e33..d7d472f1d89 100644 --- a/app/models/integrations/datadog.rb +++ b/app/models/integrations/datadog.rb @@ -2,10 +2,10 @@ module Integrations class Datadog < Integration - DEFAULT_SITE = 'datadoghq.com' - URL_TEMPLATE = 'https://webhooks-http-intake.logs.%{datadog_site}/v1/input/' - URL_TEMPLATE_API_KEYS = 'https://app.%{datadog_site}/account/settings#api' - URL_API_KEYS_DOCS = "https://docs.#{DEFAULT_SITE}/account_management/api-app-keys/" + DEFAULT_DOMAIN = 'datadoghq.com' + URL_TEMPLATE = 'https://webhooks-http-intake.logs.%{datadog_domain}/api/v2/webhook' + URL_TEMPLATE_API_KEYS = 'https://app.%{datadog_domain}/account/settings#api' + URL_API_KEYS_DOCS = "https://docs.#{DEFAULT_DOMAIN}/account_management/api-app-keys/" SUPPORTED_EVENTS = %w[ pipeline job @@ -26,7 +26,7 @@ module Integrations def initialize_properties super - self.datadog_site ||= DEFAULT_SITE + self.datadog_site ||= DEFAULT_DOMAIN end def self.supported_events @@ -62,7 +62,7 @@ module Integrations { type: 'text', name: 'datadog_site', - placeholder: DEFAULT_SITE, + placeholder: DEFAULT_DOMAIN, help: 'Choose the Datadog site to send data to. Set to "datadoghq.eu" to send data to the EU site', required: false }, @@ -105,18 +105,21 @@ module Integrations end def hook_url - url = api_url.presence || sprintf(URL_TEMPLATE, datadog_site: datadog_site) + url = api_url.presence || sprintf(URL_TEMPLATE, datadog_domain: datadog_domain) url = URI.parse(url) - url.path = File.join(url.path || '/', api_key) - query = { service: datadog_service.presence, env: datadog_env.presence }.compact - url.query = query.to_query unless query.empty? + query = { + "dd-api-key" => api_key, + service: datadog_service.presence, + env: datadog_env.presence + }.compact + url.query = query.to_query url.to_s end def api_keys_url return URL_API_KEYS_DOCS unless datadog_site.presence - sprintf(URL_TEMPLATE_API_KEYS, datadog_site: datadog_site) + sprintf(URL_TEMPLATE_API_KEYS, datadog_domain: datadog_domain) end def execute(data) @@ -137,5 +140,14 @@ module Integrations { success: true, result: result[:message] } end + + private + + def datadog_domain + # Transparently ignore "app" prefix from datadog_site as the official docs table in + # https://docs.datadoghq.com/getting_started/site/ is confusing for internal URLs. + # US3 needs to keep a prefix but other datacenters cannot have the listed "app" prefix + datadog_site.delete_prefix("app.") + end end end diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index ec50312c6d4..dc046e1d164 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -209,7 +209,7 @@ module Ci # We need to use the presenter here because Gitaly calls in the presenter # may fail, and we need to ensure the response has been generated. presented_build = ::Ci::BuildRunnerPresenter.new(build) # rubocop:disable CodeReuse/Presenter - build_json = ::API::Entities::JobRequest::Response.new(presented_build).to_json + build_json = ::API::Entities::Ci::JobRequest::Response.new(presented_build).to_json Result.new(build, build_json, true) end diff --git a/app/services/service_ping/submit_service.rb b/app/services/service_ping/submit_service.rb index 06e4fcbaf32..5c03aa46e18 100644 --- a/app/services/service_ping/submit_service.rb +++ b/app/services/service_ping/submit_service.rb @@ -20,34 +20,50 @@ module ServicePing def execute return unless ServicePing::PermitDataCategoriesService.new.product_intelligence_enabled? - usage_data = Gitlab::UsageData.data(force_refresh: true) + begin + usage_data = BuildPayloadService.new.execute + raw_usage_data, response = submit_usage_data_payload(usage_data) + rescue StandardError + return unless Gitlab::CurrentSettings.usage_ping_enabled? + + usage_data = Gitlab::UsageData.data(force_refresh: true) + raw_usage_data, response = submit_usage_data_payload(usage_data) + end - raise SubmissionError, 'Usage data is blank' if usage_data.blank? + version_usage_data_id = response.dig('conv_index', 'usage_data_id') || response.dig('dev_ops_score', 'usage_data_id') - raw_usage_data = save_raw_usage_data(usage_data) + unless version_usage_data_id.is_a?(Integer) && version_usage_data_id > 0 + raise SubmissionError, "Invalid usage_data_id in response: #{version_usage_data_id}" + end + + raw_usage_data.update_version_metadata!(usage_data_id: version_usage_data_id) + + store_metrics(response) + end + + private - response = Gitlab::HTTP.post( + def submit_payload(usage_data) + Gitlab::HTTP.post( url, body: usage_data.to_json, allow_local_requests: true, headers: { 'Content-type' => 'application/json' } ) + end - raise SubmissionError, "Unsuccessful response code: #{response.code}" unless response.success? + def submit_usage_data_payload(usage_data) + raise SubmissionError, 'Usage data is blank' if usage_data.blank? - version_usage_data_id = response.dig('conv_index', 'usage_data_id') || response.dig('dev_ops_score', 'usage_data_id') + raw_usage_data = save_raw_usage_data(usage_data) - unless version_usage_data_id.is_a?(Integer) && version_usage_data_id > 0 - raise SubmissionError, "Invalid usage_data_id in response: #{version_usage_data_id}" - end + response = submit_payload(usage_data) - raw_usage_data.update_version_metadata!(usage_data_id: version_usage_data_id) + raise SubmissionError, "Unsuccessful response code: #{response.code}" unless response.success? - store_metrics(response) + [raw_usage_data, response] end - private - def save_raw_usage_data(usage_data) RawUsageData.safe_find_or_create_by(recorded_at: usage_data[:recorded_at]) do |record| record.payload = usage_data diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index 3942cfa4643..a91c12d01ad 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -74,7 +74,7 @@ = f.text_field :build_timeout_human_readable, class: 'form-control gl-form-input' %p.form-text.text-muted = html_escape(_('Jobs fail if they run longer than the timeout time. Input value is in seconds by default. Human readable input is also accepted, for example %{code_open}1 hour%{code_close}.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe } - = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'timeout'), target: '_blank' + = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'set-a-limit-for-how-long-jobs-can-run'), target: '_blank' - if can?(current_user, :update_max_artifacts_size, @project) .form-group @@ -94,7 +94,7 @@ .input-group-text / %p.form-text.text-muted = html_escape(_('The regular expression used to find test coverage output in the job log. For example, use %{regex} for Simplecov (Ruby). Leave blank to disable.')) % { regex: '<code>\(\d+.\d+%\)</code>'.html_safe } - = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'test-coverage-parsing'), target: '_blank' + = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'add-test-coverage-results-to-a-merge-request'), target: '_blank' = f.submit _('Save changes'), class: "btn gl-button btn-confirm", data: { qa_selector: 'save_general_pipelines_changes_button' } |