diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 13:00:54 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 13:00:54 +0300 |
commit | 3cccd102ba543e02725d247893729e5c73b38295 (patch) | |
tree | f36a04ec38517f5deaaacb5acc7d949688d1e187 /app/assets/javascripts/projects | |
parent | 205943281328046ef7b4528031b90fbda70c75ac (diff) |
Add latest changes from gitlab-org/gitlab@14-10-stable-eev14.10.0-rc42
Diffstat (limited to 'app/assets/javascripts/projects')
17 files changed, 329 insertions, 27 deletions
diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue index da14b1e8470..8511f9bdb0f 100644 --- a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue +++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue @@ -3,11 +3,20 @@ import { GlLoadingIcon } from '@gitlab/ui'; import createFlash from '~/flash'; import { __ } from '~/locale'; import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; +import { + getQueryHeaders, + toggleQueryPollingByVisibility, +} from '~/pipelines/components/graph/utils'; +import { formatStages } from '../utils'; import getLinkedPipelinesQuery from '../graphql/queries/get_linked_pipelines.query.graphql'; +import getPipelineStagesQuery from '../graphql/queries/get_pipeline_stages.query.graphql'; +import { COMMIT_BOX_POLL_INTERVAL } from '../constants'; export default { i18n: { linkedPipelinesFetchError: __('There was a problem fetching linked pipelines.'), + stageConversionError: __('There was a problem handling the pipeline data.'), + stagesFetchError: __('There was a problem fetching the pipeline stages.'), }, components: { GlLoadingIcon, @@ -22,6 +31,9 @@ export default { iid: { default: '', }, + graphqlResourceEtag: { + default: '', + }, }, props: { stages: { @@ -48,10 +60,31 @@ export default { createFlash({ message: this.$options.i18n.linkedPipelinesFetchError }); }, }, + pipelineStages: { + context() { + return getQueryHeaders(this.graphqlResourceEtag); + }, + query: getPipelineStagesQuery, + pollInterval: COMMIT_BOX_POLL_INTERVAL, + variables() { + return { + fullPath: this.fullPath, + iid: this.iid, + }; + }, + update({ project }) { + return project?.pipeline?.stages?.nodes || []; + }, + error() { + createFlash({ message: this.$options.i18n.stagesFetchError }); + }, + }, }, data() { return { + formattedStages: [], pipeline: null, + pipelineStages: [], }; }, computed: { @@ -65,6 +98,25 @@ export default { return this.pipeline?.upstream; }, }, + watch: { + pipelineStages() { + // pipelineStages are from GraphQL + // stages are from REST + // we do this to use dropdown_path for fetching jobs on stage click + try { + this.formattedStages = formatStages(this.pipelineStages, this.stages); + } catch (error) { + createFlash({ + message: this.$options.i18n.stageConversionError, + captureError: true, + error, + }); + } + }, + }, + mounted() { + toggleQueryPollingByVisibility(this.$apollo.queries.pipelineStages); + }, }; </script> @@ -79,7 +131,7 @@ export default { /> <pipeline-mini-graph - :stages="stages" + :stages="formattedStages" class="gl-display-inline" data-testid="commit-box-mini-graph" /> diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_status.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_status.vue new file mode 100644 index 00000000000..5a9d3129809 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_status.vue @@ -0,0 +1,74 @@ +<script> +import { GlLoadingIcon, GlLink } from '@gitlab/ui'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import createFlash from '~/flash'; +import { + getQueryHeaders, + toggleQueryPollingByVisibility, +} from '~/pipelines/components/graph/utils'; +import getLatestPipelineStatusQuery from '../graphql/queries/get_latest_pipeline_status.query.graphql'; +import { COMMIT_BOX_POLL_INTERVAL, PIPELINE_STATUS_FETCH_ERROR } from '../constants'; + +export default { + PIPELINE_STATUS_FETCH_ERROR, + components: { + CiIcon, + GlLoadingIcon, + GlLink, + }, + inject: { + fullPath: { + default: '', + }, + iid: { + default: '', + }, + graphqlResourceEtag: { + default: '', + }, + }, + apollo: { + pipelineStatus: { + context() { + return getQueryHeaders(this.graphqlResourceEtag); + }, + query: getLatestPipelineStatusQuery, + pollInterval: COMMIT_BOX_POLL_INTERVAL, + variables() { + return { + fullPath: this.fullPath, + iid: this.iid, + }; + }, + update({ project }) { + return project?.pipeline?.detailedStatus || {}; + }, + error() { + createFlash({ message: this.$options.PIPELINE_STATUS_FETCH_ERROR }); + }, + }, + }, + data() { + return { + pipelineStatus: {}, + }; + }, + computed: { + loading() { + return this.$apollo.queries.pipelineStatus.loading; + }, + }, + mounted() { + toggleQueryPollingByVisibility(this.$apollo.queries.pipelineStatus); + }, +}; +</script> + +<template> + <div class="gl-display-inline-block gl-vertical-align-middle gl-mr-2"> + <gl-loading-icon v-if="loading" /> + <gl-link v-else :href="pipelineStatus.detailsPath"> + <ci-icon :status="pipelineStatus" :size="24" /> + </gl-link> + </div> +</template> diff --git a/app/assets/javascripts/projects/commit_box/info/constants.js b/app/assets/javascripts/projects/commit_box/info/constants.js new file mode 100644 index 00000000000..be0bf715314 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/constants.js @@ -0,0 +1,7 @@ +import { __ } from '~/locale'; + +export const COMMIT_BOX_POLL_INTERVAL = 10000; + +export const PIPELINE_STATUS_FETCH_ERROR = __( + 'There was a problem fetching the latest pipeline status.', +); diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_latest_pipeline_status.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_latest_pipeline_status.query.graphql new file mode 100644 index 00000000000..cec96f82336 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_latest_pipeline_status.query.graphql @@ -0,0 +1,14 @@ +query getLatestPipelineStatus($fullPath: ID!, $iid: ID!) { + project(fullPath: $fullPath) { + id + pipeline(iid: $iid) { + id + detailedStatus { + id + detailsPath + icon + group + } + } + } +} diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql new file mode 100644 index 00000000000..69a29947b16 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql @@ -0,0 +1,19 @@ +query getPipelineStages($fullPath: ID!, $iid: ID!) { + project(fullPath: $fullPath) { + id + pipeline(iid: $iid) { + id + stages { + nodes { + id + name + detailedStatus { + id + icon + group + } + } + } + } + } +} diff --git a/app/assets/javascripts/projects/commit_box/info/index.js b/app/assets/javascripts/projects/commit_box/info/index.js index 69fe2d30489..7500c152b6a 100644 --- a/app/assets/javascripts/projects/commit_box/info/index.js +++ b/app/assets/javascripts/projects/commit_box/info/index.js @@ -2,6 +2,7 @@ import { fetchCommitMergeRequests } from '~/commit_merge_requests'; import { initCommitPipelineMiniGraph } from './init_commit_pipeline_mini_graph'; import { initDetailsButton } from './init_details_button'; import { loadBranches } from './load_branches'; +import initCommitPipelineStatus from './init_commit_pipeline_status'; export const initCommitBoxInfo = () => { // Display commit related branches @@ -14,4 +15,6 @@ export const initCommitBoxInfo = () => { initCommitPipelineMiniGraph(); initDetailsButton(); + + initCommitPipelineStatus(); }; diff --git a/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js index 1d4ec4c110b..c206e648561 100644 --- a/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js +++ b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js @@ -5,7 +5,7 @@ import createDefaultClient from '~/lib/graphql'; Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), + defaultClient: createDefaultClient({}, { useGet: true }), }); export const initCommitPipelineMiniGraph = async (selector = '.js-commit-pipeline-mini-graph') => { @@ -15,7 +15,7 @@ export const initCommitPipelineMiniGraph = async (selector = '.js-commit-pipelin return; } - const { stages, fullPath, iid } = el.dataset; + const { stages, fullPath, iid, graphqlResourceEtag } = el.dataset; // Some commits have no pipeline, code splitting to load the pipeline optionally const { default: CommitBoxPipelineMiniGraph } = await import( @@ -30,6 +30,7 @@ export const initCommitPipelineMiniGraph = async (selector = '.js-commit-pipelin fullPath, iid, dataMethod: 'graphql', + graphqlResourceEtag, }, render(createElement) { return createElement(CommitBoxPipelineMiniGraph, { diff --git a/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_status.js b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_status.js new file mode 100644 index 00000000000..d5e62531283 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_status.js @@ -0,0 +1,34 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import CommitBoxPipelineStatus from './components/commit_box_pipeline_status.vue'; + +Vue.use(VueApollo); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient({}, { useGet: true }), +}); + +export default (selector = '.js-commit-pipeline-status') => { + const el = document.querySelector(selector); + + if (!el) { + return; + } + + const { fullPath, iid, graphqlResourceEtag } = el.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el, + apolloProvider, + provide: { + fullPath, + iid, + graphqlResourceEtag, + }, + render(createElement) { + return createElement(CommitBoxPipelineStatus); + }, + }); +}; diff --git a/app/assets/javascripts/projects/commit_box/info/utils.js b/app/assets/javascripts/projects/commit_box/info/utils.js new file mode 100644 index 00000000000..ea7eb35cbaf --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/utils.js @@ -0,0 +1,14 @@ +export const formatStages = (graphQLStages = [], restStages = []) => { + if (graphQLStages.length !== restStages.length) { + throw new Error('Rest stages and graphQl stages must be the same length'); + } + + return graphQLStages.map((stage, index) => { + return { + name: stage.name, + status: stage.detailedStatus, + dropdown_path: restStages[index]?.dropdown_path || '', + title: restStages[index].title || '', + }; + }); +}; diff --git a/app/assets/javascripts/projects/components/shared/delete_button.vue b/app/assets/javascripts/projects/components/shared/delete_button.vue index fd71a246a26..277af2f281e 100644 --- a/app/assets/javascripts/projects/components/shared/delete_button.vue +++ b/app/assets/javascripts/projects/components/shared/delete_button.vue @@ -104,7 +104,6 @@ export default { <gl-modal ref="removeModal" :modal-id="modalId" - size="sm" ok-variant="danger" footer-class="gl-bg-gray-10 gl-p-5" title-class="gl-text-red-500" diff --git a/app/assets/javascripts/projects/default_project_templates.js b/app/assets/javascripts/projects/default_project_templates.js index 0393d82ca36..6708b7bd9e2 100644 --- a/app/assets/javascripts/projects/default_project_templates.js +++ b/app/assets/javascripts/projects/default_project_templates.js @@ -57,9 +57,9 @@ export default { text: s__('ProjectTemplates|Pages/Hexo'), icon: '.template-option .icon-hexo', }, - sse_middleman: { - text: s__('ProjectTemplates|Static Site Editor/Middleman'), - icon: '.template-option .icon-sse_middleman', + middleman: { + text: s__('ProjectTemplates|Pages/Middleman'), + icon: '.template-option .icon-middleman', }, gitpod_spring_petclinic: { text: s__('ProjectTemplates|Gitpod/Spring Petclinic'), diff --git a/app/assets/javascripts/projects/new/components/deployment_target_select.vue b/app/assets/javascripts/projects/new/components/deployment_target_select.vue index f3b7e39f148..0003134f15c 100644 --- a/app/assets/javascripts/projects/new/components/deployment_target_select.vue +++ b/app/assets/javascripts/projects/new/components/deployment_target_select.vue @@ -1,12 +1,15 @@ <script> -import { GlFormGroup, GlFormSelect } from '@gitlab/ui'; +import { GlFormGroup, GlFormSelect, GlFormText, GlSprintf, GlLink } from '@gitlab/ui'; +import { helpPagePath } from '~/helpers/help_page_helper'; import { s__ } from '~/locale'; import Tracking from '~/tracking'; import { DEPLOYMENT_TARGET_SELECTIONS, DEPLOYMENT_TARGET_LABEL, DEPLOYMENT_TARGET_EVENT, + VISIT_DOCS_EVENT, NEW_PROJECT_FORM, + K8S_OPTION, } from '../constants'; const trackingMixin = Tracking.mixin({ label: DEPLOYMENT_TARGET_LABEL }); @@ -15,12 +18,21 @@ export default { i18n: { deploymentTargetLabel: s__('Deployment Target|Project deployment target (optional)'), defaultOption: s__('Deployment Target|Select the deployment target'), + k8sEducationText: s__( + 'Deployment Target|%{linkStart}How to provision or deploy to Kubernetes clusters from GitLab?%{linkEnd}', + ), }, deploymentTargets: DEPLOYMENT_TARGET_SELECTIONS, + VISIT_DOCS_EVENT, + DEPLOYMENT_TARGET_LABEL, selectId: 'deployment-target-select', + helpPageUrl: helpPagePath('user/clusters/agent/index'), components: { GlFormGroup, GlFormSelect, + GlFormText, + GlSprintf, + GlLink, }, mixins: [trackingMixin], data() { @@ -29,6 +41,11 @@ export default { formSubmitted: false, }; }, + computed: { + isK8sOptionSelected() { + return this.selectedTarget === K8S_OPTION; + }, + }, mounted() { const form = document.getElementById(NEW_PROJECT_FORM); form.addEventListener('submit', () => { @@ -52,10 +69,24 @@ export default { :id="$options.selectId" v-model="selectedTarget" :options="$options.deploymentTargets" + class="input-lg" > <template #first> <option :value="null" disabled>{{ $options.i18n.defaultOption }}</option> </template> </gl-form-select> + + <gl-form-text v-if="isK8sOptionSelected"> + <gl-sprintf :message="$options.i18n.k8sEducationText"> + <template #link="{ content }"> + <gl-link + :href="$options.helpPageUrl" + :data-track-action="$options.VISIT_DOCS_EVENT" + :data-track-label="$options.DEPLOYMENT_TARGET_LABEL" + >{{ content }}</gl-link + > + </template> + </gl-sprintf> + </gl-form-text> </gl-form-group> </template> diff --git a/app/assets/javascripts/projects/new/components/new_project_url_select.vue b/app/assets/javascripts/projects/new/components/new_project_url_select.vue index f4a21c6057c..506f1ec5ffd 100644 --- a/app/assets/javascripts/projects/new/components/new_project_url_select.vue +++ b/app/assets/javascripts/projects/new/components/new_project_url_select.vue @@ -13,6 +13,7 @@ import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import Tracking from '~/tracking'; import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { s__ } from '~/locale'; import searchNamespacesWhereUserCanCreateProjectsQuery from '../queries/search_namespaces_where_user_can_create_projects.query.graphql'; import eventHub from '../event_hub'; @@ -43,14 +44,7 @@ export default { debounce: DEBOUNCE_DELAY, }, }, - inject: [ - 'namespaceFullPath', - 'namespaceId', - 'rootUrl', - 'trackLabel', - 'userNamespaceFullPath', - 'userNamespaceId', - ], + inject: ['namespaceFullPath', 'namespaceId', 'rootUrl', 'trackLabel', 'userNamespaceId'], data() { return { currentUser: {}, @@ -62,10 +56,11 @@ export default { fullPath: this.namespaceFullPath, } : { - id: this.userNamespaceId, - fullPath: this.userNamespaceFullPath, + id: undefined, + fullPath: s__('ProjectsNew|Pick a group or namespace'), }, shouldSkipQuery: true, + userNamespaceId: this.userNamespaceId, }; }, computed: { @@ -92,6 +87,9 @@ export default { hasNoMatches() { return !this.hasGroupMatches && !this.hasNamespaceMatches; }, + dropdownPlaceholderClass() { + return this.selectedNamespace.id ? '' : 'gl-text-gray-500!'; + }, }, created() { eventHub.$on('select-template', this.handleSelectTemplate); @@ -130,11 +128,18 @@ export default { </script> <template> - <gl-button-group class="input-lg"> - <gl-button class="gl-text-truncate" label :title="rootUrl">{{ rootUrl }}</gl-button> + <gl-button-group class="gl-w-full"> + <gl-button + class="js-group-namespace-button gl-text-truncate gl-flex-grow-0!" + label + :title="rootUrl" + >{{ rootUrl }}</gl-button + > + <gl-dropdown :text="selectedNamespace.fullPath" - toggle-class="gl-rounded-top-right-base! gl-rounded-bottom-right-base! gl-w-20" + class="js-group-namespace-dropdown gl-flex-grow-1" + :toggle-class="`gl-rounded-top-right-base! gl-rounded-bottom-right-base! gl-w-20 ${dropdownPlaceholderClass}`" data-qa-selector="select_namespace_dropdown" @show="track('activate_form_input', { label: trackLabel, property: 'project_path' })" @shown="handleDropdownShown" @@ -166,11 +171,13 @@ export default { </template> </gl-dropdown> + <input type="hidden" name="project[selected_namespace_id]" :value="selectedNamespace.id" /> + <input id="project_namespace_id" type="hidden" name="project[namespace_id]" - :value="selectedNamespace.id" + :value="selectedNamespace.id || userNamespaceId" /> </gl-button-group> </template> diff --git a/app/assets/javascripts/projects/new/constants.js b/app/assets/javascripts/projects/new/constants.js index c5e6722981b..e52a84dc07e 100644 --- a/app/assets/javascripts/projects/new/constants.js +++ b/app/assets/javascripts/projects/new/constants.js @@ -1,7 +1,9 @@ import { s__ } from '~/locale'; +export const K8S_OPTION = s__('DeploymentTarget|Kubernetes (GKE, EKS, OpenShift, and so on)'); + export const DEPLOYMENT_TARGET_SELECTIONS = [ - s__('DeploymentTarget|Kubernetes (GKE, EKS, OpenShift, and so on)'), + K8S_OPTION, s__('DeploymentTarget|Managed container runtime (Fargate, Cloud Run, DigitalOcean App)'), s__('DeploymentTarget|Self-managed container runtime (Podman, Docker Swarm, Docker Compose)'), s__('DeploymentTarget|Heroku'), @@ -18,3 +20,4 @@ export const DEPLOYMENT_TARGET_SELECTIONS = [ export const NEW_PROJECT_FORM = 'new_project'; export const DEPLOYMENT_TARGET_LABEL = 'new_project_deployment_target'; export const DEPLOYMENT_TARGET_EVENT = 'select_deployment_target'; +export const VISIT_DOCS_EVENT = 'visit_docs'; diff --git a/app/assets/javascripts/projects/new/index.js b/app/assets/javascripts/projects/new/index.js index 4de9b8a6f47..a72172a4f5e 100644 --- a/app/assets/javascripts/projects/new/index.js +++ b/app/assets/javascripts/projects/new/index.js @@ -58,7 +58,6 @@ export function initNewProjectUrlSelect() { namespaceId: el.dataset.namespaceId, rootUrl: el.dataset.rootUrl, trackLabel: el.dataset.trackLabel, - userNamespaceFullPath: el.dataset.userNamespaceFullPath, userNamespaceId: el.dataset.userNamespaceId, }, render: (createElement) => createElement(NewProjectUrlSelect), diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index f1b7e3df7d6..3e1c471f015 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -3,6 +3,7 @@ import { debounce } from 'lodash'; import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '../lib/utils/constants'; +import { ENTER_KEY } from '../lib/utils/keys'; import axios from '../lib/utils/axios_utils'; import { convertToTitleCase, @@ -14,6 +15,7 @@ import { let hasUserDefinedProjectPath = false; let hasUserDefinedProjectName = false; const invalidInputClass = 'gl-field-error-outline'; +const invalidDropdownClass = 'gl-inset-border-1-red-400!'; const cancelSource = axios.CancelToken.source(); const endpoint = `${gon.relative_url_root}/import/url/validate`; @@ -50,6 +52,25 @@ const onProjectPathChange = ($projectNameInput, $projectPathInput, hasExistingPr } }; +const selectedNamespaceId = () => document.querySelector('[name="project[selected_namespace_id]"]'); +const dropdownButton = () => document.querySelector('.js-group-namespace-dropdown > button'); +const namespaceButton = () => document.querySelector('.js-group-namespace-button'); +const namespaceError = () => document.querySelector('.js-group-namespace-error'); + +const validateGroupNamespaceDropdown = (e) => { + if (selectedNamespaceId() && !selectedNamespaceId().attributes.value) { + document.querySelector('input[data-qa-selector="project_name"]').reportValidity(); + e.preventDefault(); + dropdownButton().classList.add(invalidDropdownClass); + namespaceButton().classList.add(invalidDropdownClass); + namespaceError().classList.remove('gl-display-none'); + } else { + dropdownButton().classList.remove(invalidDropdownClass); + namespaceButton().classList.remove(invalidDropdownClass); + namespaceError().classList.add('gl-display-none'); + } +}; + const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => { const specialRepo = document.querySelector('.js-user-readme-repo'); @@ -70,6 +91,10 @@ const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => { $projectPathInput.val() !== $projectPathInput.data('username'), ); }); + + document.querySelector('.js-create-project-button').addEventListener('click', (e) => { + validateGroupNamespaceDropdown(e); + }); }; const deriveProjectPathFromUrl = ($projectImportUrl) => { @@ -158,7 +183,11 @@ const bindEvents = () => { $projectTemplateButtons.addClass('hidden'); $projectFieldsForm.addClass('selected'); $selectedIcon.empty(); - const value = $(this).val(); + + const $selectedTemplate = $(this); + $selectedTemplate.prop('checked', true); + + const value = $selectedTemplate.val(); const selectedTemplate = DEFAULT_PROJECT_TEMPLATES[value]; $selectedTemplateText.text(selectedTemplate.text); @@ -170,7 +199,21 @@ const bindEvents = () => { setProjectNamePathHandlers($activeTabProjectName, $activeTabProjectPath); } - $useTemplateBtn.on('change', chooseTemplate); + function toggleActiveClassOnLabel(event) { + const $label = $(event.target).parent(); + $label.toggleClass('active'); + } + + function chooseTemplateOnEnter(event) { + if (event.code === ENTER_KEY) { + chooseTemplate.call(this); + } + } + + $useTemplateBtn.on('click', chooseTemplate); + + $useTemplateBtn.on('focus focusout', toggleActiveClassOnLabel); + $useTemplateBtn.on('keypress', chooseTemplateOnEnter); $changeTemplateBtn.on('click', () => { $projectTemplateButtons.removeClass('hidden'); diff --git a/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue index e8b0e95b142..d4c97cbf038 100644 --- a/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue +++ b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue @@ -1,6 +1,7 @@ <script> import { GlTokenSelector, GlAvatarLabeled } from '@gitlab/ui'; import { s__ } from '~/locale'; +import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants'; import searchProjectTopics from '../queries/project_topics_search.query.graphql'; export default { @@ -65,6 +66,7 @@ export default { this.$emit('update', tokens); }, }, + AVATAR_SHAPE_OPTION_RECT, }; </script> <template> @@ -85,7 +87,7 @@ export default { :entity-name="dropdownItem.name" :label="dropdownItem.name" :size="32" - shape="rect" + :shape="$options.AVATAR_SHAPE_OPTION_RECT" /> </template> </gl-token-selector> |