diff options
Diffstat (limited to 'app/assets/javascripts/ci')
89 files changed, 828 insertions, 1215 deletions
diff --git a/app/assets/javascripts/ci/admin/jobs_table/admin_jobs_table_app.vue b/app/assets/javascripts/ci/admin/jobs_table/admin_jobs_table_app.vue index 89582e64f3a..55ff647e25f 100644 --- a/app/assets/javascripts/ci/admin/jobs_table/admin_jobs_table_app.vue +++ b/app/assets/javascripts/ci/admin/jobs_table/admin_jobs_table_app.vue @@ -84,9 +84,6 @@ export default { update(data) { return data?.jobs?.count || 0; }, - context: { - isSingleRequest: true, - }, error() { this.error = this.$options.i18n.jobsCountErrorMsg; }, diff --git a/app/assets/javascripts/ci/artifacts/components/job_artifacts_table.vue b/app/assets/javascripts/ci/artifacts/components/job_artifacts_table.vue index d8f9eb65236..de37aa431e6 100644 --- a/app/assets/javascripts/ci/artifacts/components/job_artifacts_table.vue +++ b/app/assets/javascripts/ci/artifacts/components/job_artifacts_table.vue @@ -10,7 +10,7 @@ import { GlFormCheckbox, GlTooltipDirective, } from '@gitlab/ui'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { createAlert } from '~/alert'; import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; @@ -68,7 +68,7 @@ export default { GlPagination, GlFormCheckbox, TimeAgo, - CiBadgeLink, + CiIcon, JobCheckbox, ArtifactsBulkDelete, BulkDeleteModal, @@ -442,7 +442,7 @@ export default { <template #cell(job)="{ item }"> <div class="gl-display-inline-flex gl-align-items-center gl-mb-3 gl-gap-3"> <span data-testid="job-artifacts-job-status"> - <ci-badge-link :status="item.detailedStatus" size="sm" :show-text="false" /> + <ci-icon :status="item.detailedStatus" /> </span> <gl-link :href="item.webPath"> {{ item.name }} diff --git a/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue b/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue index 85dfa12c756..fbc7ddf5c91 100644 --- a/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue +++ b/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue @@ -1,11 +1,13 @@ <script> -import { GlLoadingIcon, GlTableLite } from '@gitlab/ui'; +import { GlButton, GlEmptyState, GlLoadingIcon, GlTableLite } from '@gitlab/ui'; import { createAlert } from '~/alert'; import { __, s__ } from '~/locale'; import getCiCatalogResourceComponents from '../../graphql/queries/get_ci_catalog_resource_components.query.graphql'; export default { components: { + GlButton, + GlEmptyState, GlLoadingIcon, GlTableLite, }, @@ -37,6 +39,9 @@ export default { }, }, computed: { + isMetadataMissing() { + return !this.components || this.components?.length === 0; + }, isLoading() { return this.$apollo.queries.components.loading; }, @@ -70,6 +75,12 @@ export default { }, ], i18n: { + copyText: __('Copy value'), + copyAriaText: __('Copy to clipboard'), + emptyStateTitle: s__('CiCatalogComponent|Component details not available'), + emptyStateDesc: s__( + 'CiCatalogComponent|This tab displays auto-collected information about the components in the repository, but no information was found.', + ), inputTitle: s__('CiCatalogComponent|Inputs'), fetchError: s__("CiCatalogComponent|There was an error fetching this resource's components"), }, @@ -79,6 +90,11 @@ export default { <template> <div> <gl-loading-icon v-if="isLoading" size="lg" /> + <gl-empty-state + v-else-if="isMetadataMissing" + :title="$options.i18n.emptyStateTitle" + :description="$options.i18n.emptyStateDesc" + /> <template v-else> <div v-for="component in components" @@ -88,7 +104,24 @@ export default { > <h3 class="gl-font-size-h2" data-testid="component-name">{{ component.name }}</h3> <p class="gl-mt-5">{{ component.description }}</p> - <pre class="gl-w-85p gl-py-4">{{ generateSnippet(component.path) }}</pre> + <div class="gl-display-flex"> + <pre + class="gl-w-85p gl-py-4 gl-display-flex gl-justify-content-space-between gl-m-0 gl-border-r-none" + ><span>{{ generateSnippet(component.path) }}</span> + </pre> + <div class="gl--flex-center gl-bg-gray-10 gl-border gl-border-l-none"> + <gl-button + class="gl-p-4! gl-mr-3!" + category="tertiary" + icon="copy-to-clipboard" + size="small" + :title="$options.i18n.copyText" + :data-clipboard-text="generateSnippet(component.path)" + data-testid="copy-to-clipboard" + :aria-label="$options.i18n.copyAriaText" + /> + </div> + </div> <div class="gl-mt-5"> <b class="gl-display-block gl-mb-4"> {{ $options.i18n.inputTitle }}</b> <gl-table-lite :items="component.inputs.nodes" :fields="$options.fields"> diff --git a/app/assets/javascripts/ci/catalog/components/details/ci_resource_details.vue b/app/assets/javascripts/ci/catalog/components/details/ci_resource_details.vue index c0feb52c185..026a30988fd 100644 --- a/app/assets/javascripts/ci/catalog/components/details/ci_resource_details.vue +++ b/app/assets/javascripts/ci/catalog/components/details/ci_resource_details.vue @@ -30,12 +30,12 @@ export default { <template> <gl-tabs> - <gl-tab v-if="glFeatures.ciCatalogComponentsTab" :title="$options.i18n.tabs.components" lazy> - <ci-resource-components :resource-id="resourceId" - /></gl-tab> <gl-tab :title="$options.i18n.tabs.readme" lazy> <ci-resource-readme :resource-id="resourceId" /> </gl-tab> + <gl-tab v-if="glFeatures.ciCatalogComponentsTab" :title="$options.i18n.tabs.components" lazy> + <ci-resource-components :resource-id="resourceId" + /></gl-tab> </gl-tabs> </template> <style></style> diff --git a/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue b/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue index 6673785ffd2..29009c14e1b 100644 --- a/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue +++ b/app/assets/javascripts/ci/catalog/components/details/ci_resource_header.vue @@ -2,13 +2,13 @@ import { GlAvatar, GlAvatarLink, GlBadge } from '@gitlab/ui'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { isNumeric } from '~/lib/utils/number_utils'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CiResourceAbout from './ci_resource_about.vue'; import CiResourceHeaderSkeletonLoader from './ci_resource_header_skeleton_loader.vue'; export default { components: { - CiBadgeLink, + CiIcon, CiResourceAbout, CiResourceHeaderSkeletonLoader, GlAvatar, @@ -102,12 +102,11 @@ export default { {{ versionBadgeText }} </gl-badge> </span> - <ci-badge-link + <ci-icon v-if="hasPipelineStatus" - class="gl-mt-2" :status="pipelineStatus" - size="sm" - show-text + show-status-text + class="gl-mt-2" /> </div> </div> diff --git a/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue b/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue index 487215875c0..db84eaa82c2 100644 --- a/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue +++ b/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue @@ -4,12 +4,22 @@ import { __, s__ } from '~/locale'; import { helpPagePath } from '~/helpers/help_page_helper'; import { CATALOG_FEEDBACK_DISMISSED_KEY } from '../../constants'; +const defaultTitle = __('CI/CD Catalog'); +const defaultDescription = s__( + 'CiCatalog|Discover CI configuration resources for a seamless CI/CD experience.', +); + export default { components: { GlBanner, GlLink, }, - inject: ['pageTitle', 'pageDescription'], + inject: { + pageTitle: { default: defaultTitle }, + pageDescription: { + default: defaultDescription, + }, + }, data() { return { isFeedbackBannerDismissed: localStorage.getItem(CATALOG_FEEDBACK_DISMISSED_KEY) === 'true', @@ -50,7 +60,7 @@ export default { </gl-banner> <h1 class="gl-font-size-h-display">{{ pageTitle }}</h1> <p> - <span>{{ pageDescription }}</span> + <span data-testid="description">{{ pageDescription }}</span> <gl-link :href="$options.learnMorePath" target="_blank">{{ $options.i18n.learnMore }}</gl-link> diff --git a/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue b/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue index 63243539575..080955b4322 100644 --- a/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue +++ b/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue @@ -48,9 +48,6 @@ export default { starCount() { return this.resource?.starCount || 0; }, - forksCount() { - return this.resource?.forksCount || 0; - }, hasReleasedVersion() { return Boolean(this.latestVersion?.releasedAt); }, @@ -111,14 +108,12 @@ export default { <gl-icon name="star" :size="14" class="gl-mr-1" /> <span class="gl-mr-3">{{ starCount }}</span> </span> - <span class="gl--flex-center" data-testid="stats-forks"> - <gl-icon name="fork" :size="14" class="gl-mr-1" /> - <span>{{ forksCount }}</span> - </span> </span> </div> </div> - <div class="gl-display-flex gl-sm-flex-direction-column gl-justify-content-space-between"> + <div + class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-justify-content-space-between" + > <span class="gl-display-flex gl-flex-basis-two-thirds gl-font-sm">{{ resource.description }}</span> diff --git a/app/assets/javascripts/ci/catalog/components/pages/ci_resources_page.vue b/app/assets/javascripts/ci/catalog/components/pages/ci_resources_page.vue new file mode 100644 index 00000000000..5e8727a3ed0 --- /dev/null +++ b/app/assets/javascripts/ci/catalog/components/pages/ci_resources_page.vue @@ -0,0 +1,112 @@ +<script> +import { createAlert } from '~/alert'; +import { s__ } from '~/locale'; +import CatalogHeader from '~/ci/catalog/components/list/catalog_header.vue'; +import CatalogListSkeletonLoader from '~/ci/catalog/components/list/catalog_list_skeleton_loader.vue'; +import CiResourcesList from '~/ci/catalog/components/list/ci_resources_list.vue'; +import EmptyState from '~/ci/catalog/components/list/empty_state.vue'; +import { ciCatalogResourcesItemsCount } from '~/ci/catalog/graphql/settings'; +import getCatalogResources from '../../graphql/queries/get_ci_catalog_resources.query.graphql'; + +export default { + components: { + CatalogHeader, + CatalogListSkeletonLoader, + CiResourcesList, + EmptyState, + }, + data() { + return { + catalogResources: [], + currentPage: 1, + totalCount: 0, + pageInfo: {}, + }; + }, + apollo: { + catalogResources: { + query: getCatalogResources, + variables() { + return { + first: ciCatalogResourcesItemsCount, + }; + }, + update(data) { + return data?.ciCatalogResources?.nodes || []; + }, + result({ data }) { + const { pageInfo } = data?.ciCatalogResources || {}; + this.pageInfo = pageInfo; + this.totalCount = data?.ciCatalogResources?.count || 0; + }, + error(e) { + createAlert({ message: e.message || this.$options.i18n.fetchError, variant: 'danger' }); + }, + }, + }, + computed: { + hasResources() { + return this.catalogResources.length > 0; + }, + isLoading() { + return this.$apollo.queries.catalogResources.loading; + }, + }, + methods: { + async handlePrevPage() { + try { + await this.$apollo.queries.catalogResources.fetchMore({ + variables: { + before: this.pageInfo.startCursor, + last: ciCatalogResourcesItemsCount, + first: null, + }, + }); + + this.currentPage -= 1; + } catch (e) { + // Ensure that the current query is properly stoped if an error occurs. + this.$apollo.queries.catalogResources.stop(); + createAlert({ message: e?.message || this.$options.i18n.fetchError, variant: 'danger' }); + } + }, + async handleNextPage() { + try { + await this.$apollo.queries.catalogResources.fetchMore({ + variables: { + after: this.pageInfo.endCursor, + }, + }); + + this.currentPage += 1; + } catch (e) { + // Ensure that the current query is properly stoped if an error occurs. + this.$apollo.queries.catalogResources.stop(); + + createAlert({ message: e?.message || this.$options.i18n.fetchError, variant: 'danger' }); + } + }, + }, + i18n: { + fetchError: s__('CiCatalog|There was an error fetching CI/CD Catalog resources.'), + }, +}; +</script> +<template> + <div> + <catalog-header /> + <catalog-list-skeleton-loader v-if="isLoading" class="gl-w-full gl-mt-3" /> + <empty-state v-else-if="!hasResources" /> + <ci-resources-list + v-else + :current-page="currentPage" + :page-info="pageInfo" + :prev-text="__('Prev')" + :next-text="__('Next')" + :resources="catalogResources" + :total-count="totalCount" + @onPrevPage="handlePrevPage" + @onNextPage="handleNextPage" + /> + </div> +</template> diff --git a/app/assets/javascripts/ci/catalog/global_catalog.vue b/app/assets/javascripts/ci/catalog/global_catalog.vue new file mode 100644 index 00000000000..76eac11a122 --- /dev/null +++ b/app/assets/javascripts/ci/catalog/global_catalog.vue @@ -0,0 +1,10 @@ +<script> +import CiCatalogHome from './components/ci_catalog_home.vue'; + +export default { + components: { CiCatalogHome }, +}; +</script> +<template> + <ci-catalog-home /> +</template> diff --git a/app/assets/javascripts/ci/catalog/graphql/fragments/catalog_resource.fragment.graphql b/app/assets/javascripts/ci/catalog/graphql/fragments/catalog_resource.fragment.graphql index f4d1bb0eaaf..a86db4c1b03 100644 --- a/app/assets/javascripts/ci/catalog/graphql/fragments/catalog_resource.fragment.graphql +++ b/app/assets/javascripts/ci/catalog/graphql/fragments/catalog_resource.fragment.graphql @@ -4,7 +4,6 @@ fragment CatalogResourceFields on CiCatalogResource { name description starCount - forksCount latestVersion { id tagName diff --git a/app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resources.query.graphql b/app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resources.query.graphql new file mode 100644 index 00000000000..aae29edef5e --- /dev/null +++ b/app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resources.query.graphql @@ -0,0 +1,16 @@ +#import "~/ci/catalog/graphql/fragments/catalog_resource.fragment.graphql" + +query getCatalogResources($after: String, $before: String, $first: Int = 20, $last: Int) { + ciCatalogResources(after: $after, before: $before, first: $first, last: $last) { + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + count + nodes { + ...CatalogResourceFields + } + } +} diff --git a/app/assets/javascripts/ci/catalog/index.js b/app/assets/javascripts/ci/catalog/index.js new file mode 100644 index 00000000000..5815245506c --- /dev/null +++ b/app/assets/javascripts/ci/catalog/index.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import { cacheConfig, resolvers } from '~/ci/catalog/graphql/settings'; + +import GlobalCatalog from './global_catalog.vue'; +import CiResourcesPage from './components/pages/ci_resources_page.vue'; +import { createRouter } from './router'; + +export const initCatalog = (selector = '#js-ci-cd-catalog') => { + const el = document.querySelector(selector); + if (!el) { + return null; + } + + const { dataset } = el; + const { ciCatalogPath } = dataset; + + Vue.use(VueApollo); + + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(resolvers, cacheConfig), + }); + + return new Vue({ + el, + name: 'GlobalCatalog', + router: createRouter(ciCatalogPath, CiResourcesPage), + apolloProvider, + provide: { + ciCatalogPath, + }, + render(h) { + return h(GlobalCatalog); + }, + }); +}; diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue index a32c5f476fb..ccfe773b01f 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue @@ -190,6 +190,11 @@ export default { deep: true, }, }, + beforeMount() { + // reset to default environments list every time we open the drawer + // and re-render the environments scope dropdown + this.$emit('search-environment-scope', ''); + }, mounted() { if (this.isProtectedByDefault && !this.isEditing) { this.variable = { ...this.variable, protected: true }; @@ -371,7 +376,6 @@ export default { :label-text="$options.i18n.key" class="gl-border-none gl-pb-0! gl-mb-n5" data-testid="ci-variable-key" - data-qa-selector="ci_variable_key_field" /> <gl-form-group :label="$options.i18n.value" @@ -388,7 +392,6 @@ export default { rows="3" max-rows="10" data-testid="ci-variable-value" - data-qa-selector="ci_variable_value_field" spellcheck="false" /> <p @@ -419,15 +422,14 @@ export default { variant="danger" category="secondary" class="gl-mr-3" - data-testid="ci-variable-delete-btn" + data-testid="ci-variable-delete-button" >{{ $options.i18n.deleteVariable }}</gl-button > <gl-button category="primary" variant="confirm" :disabled="!canSubmit" - data-testid="ci-variable-confirm-btn" - data-qa-selector="ci_variable_save_button" + data-testid="ci-variable-confirm-button" @click="submit" >{{ modalActionText }} </gl-button> diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue deleted file mode 100644 index cc664d76267..00000000000 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue +++ /dev/null @@ -1,511 +0,0 @@ -<script> -import { - GlAlert, - GlButton, - GlCollapse, - GlFormCheckbox, - GlFormCombobox, - GlFormGroup, - GlFormSelect, - GlFormInput, - GlFormTextarea, - GlIcon, - GlLink, - GlModal, - GlSprintf, -} from '@gitlab/ui'; -import { helpPagePath } from '~/helpers/help_page_helper'; -import { getCookie, setCookie } from '~/lib/utils/common_utils'; -import { __ } from '~/locale'; -import Tracking from '~/tracking'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; - -import { - allEnvironments, - AWS_TOKEN_CONSTANTS, - ADD_CI_VARIABLE_MODAL_ID, - AWS_TIP_DISMISSED_COOKIE_NAME, - AWS_TIP_TITLE, - AWS_TIP_MESSAGE, - CONTAINS_VARIABLE_REFERENCE_MESSAGE, - defaultVariableState, - ENVIRONMENT_SCOPE_LINK_TITLE, - EVENT_LABEL, - EVENT_ACTION, - EXPANDED_VARIABLES_NOTE, - EDIT_VARIABLE_ACTION, - FLAG_LINK_TITLE, - VARIABLE_ACTIONS, - variableOptions, -} from '../constants'; -import CiEnvironmentsDropdown from './ci_environments_dropdown.vue'; -import { awsTokens, awsTokenList } from './ci_variable_autocomplete_tokens'; - -const trackingMixin = Tracking.mixin({ label: EVENT_LABEL }); - -export default { - components: { - CiEnvironmentsDropdown, - GlAlert, - GlButton, - GlCollapse, - GlFormCheckbox, - GlFormCombobox, - GlFormGroup, - GlFormSelect, - GlFormInput, - GlFormTextarea, - GlIcon, - GlLink, - GlModal, - GlSprintf, - }, - mixins: [glFeatureFlagsMixin(), trackingMixin], - inject: [ - 'containsVariableReferenceLink', - 'environmentScopeLink', - 'isProtectedByDefault', - 'maskedEnvironmentVariablesLink', - 'maskableRawRegex', - 'maskableRegex', - ], - props: { - areEnvironmentsLoading: { - type: Boolean, - required: true, - }, - areScopedVariablesAvailable: { - type: Boolean, - required: false, - default: false, - }, - environments: { - type: Array, - required: false, - default: () => [], - }, - hideEnvironmentScope: { - type: Boolean, - required: false, - default: false, - }, - mode: { - type: String, - required: true, - validator(val) { - return VARIABLE_ACTIONS.includes(val); - }, - }, - selectedVariable: { - type: Object, - required: false, - default: () => {}, - }, - variables: { - type: Array, - required: false, - default: () => [], - }, - }, - data() { - return { - newEnvironments: [], - isTipDismissed: getCookie(AWS_TIP_DISMISSED_COOKIE_NAME) === 'true', - validationErrorEventProperty: '', - variable: { ...defaultVariableState, ...this.selectedVariable }, - }; - }, - computed: { - canMask() { - const regex = RegExp(this.useRawMaskableRegexp ? this.maskableRawRegex : this.maskableRegex); - return regex.test(this.variable.value); - }, - canSubmit() { - return this.variableValidationState && this.variable.key !== ''; - }, - containsVariableReference() { - const regex = /\$/; - return regex.test(this.variable.value) && this.isExpanded; - }, - displayMaskedError() { - return !this.canMask && this.variable.masked; - }, - isEditing() { - return this.mode === EDIT_VARIABLE_ACTION; - }, - isExpanded() { - return !this.isRaw; - }, - isRaw() { - return this.variable.raw; - }, - isTipVisible() { - return !this.isTipDismissed && AWS_TOKEN_CONSTANTS.includes(this.variable.key); - }, - maskedFeedback() { - return this.displayMaskedError - ? __('This variable value does not meet the masking requirements.') - : ''; - }, - maskedState() { - if (this.displayMaskedError) { - return false; - } - return true; - }, - modalActionText() { - return this.isEditing ? __('Update variable') : __('Add variable'); - }, - tokenValidationFeedback() { - const tokenSpecificFeedback = this.$options.tokens?.[this.variable.key]?.invalidMessage; - if (!this.tokenValidationState && tokenSpecificFeedback) { - return tokenSpecificFeedback; - } - return ''; - }, - tokenValidationState() { - const validator = this.$options.tokens?.[this.variable.key]?.validation; - - if (validator) { - return validator(this.variable.value); - } - - return true; - }, - useRawMaskableRegexp() { - return this.isRaw; - }, - variableValidationFeedback() { - return `${this.tokenValidationFeedback} ${this.maskedFeedback}`; - }, - variableValidationState() { - return this.variable.value === '' || (this.tokenValidationState && this.maskedState); - }, - variableValueHelpText() { - return this.variable.masked - ? __('Value must meet regular expression requirements to be masked.') - : ''; - }, - }, - watch: { - variable: { - handler() { - this.trackVariableValidationErrors(); - }, - deep: true, - }, - }, - methods: { - addVariable() { - this.$emit('add-variable', this.variable); - }, - deleteVariable() { - this.$emit('delete-variable', this.variable); - }, - updateVariable() { - this.$emit('update-variable', this.variable); - }, - dismissTip() { - setCookie(AWS_TIP_DISMISSED_COOKIE_NAME, 'true', { expires: 90 }); - this.isTipDismissed = true; - }, - deleteVarAndClose() { - this.deleteVariable(); - this.hideModal(); - }, - hideModal() { - this.$refs.modal.hide(); - }, - onShow() { - this.setVariableProtectedByDefault(); - }, - resetModalHandler() { - this.resetVariableData(); - this.resetValidationErrorEvents(); - - this.$emit('close-form'); - }, - resetVariableData() { - this.variable = { ...defaultVariableState }; - }, - setEnvironmentScope(scope) { - this.variable = { ...this.variable, environmentScope: scope }; - }, - setVariableRaw(expanded) { - this.variable = { ...this.variable, raw: !expanded }; - }, - setVariableProtected() { - this.variable = { ...this.variable, protected: true }; - }, - updateOrAddVariable() { - if (this.isEditing) { - this.updateVariable(); - } else { - this.addVariable(); - } - this.hideModal(); - }, - setVariableProtectedByDefault() { - if (this.isProtectedByDefault && !this.isEditing) { - this.setVariableProtected(); - } - }, - trackVariableValidationErrors() { - const property = this.getTrackingErrorProperty(); - if (!this.validationErrorEventProperty && property) { - this.track(EVENT_ACTION, { property }); - this.validationErrorEventProperty = property; - } - }, - getTrackingErrorProperty() { - let property; - if (this.variable.value?.length && !property) { - if (this.displayMaskedError && this.maskableRegex?.length) { - const supportedChars = this.maskableRegex.replace('^', '').replace(/{(\d,)}\$/, ''); - const regex = new RegExp(supportedChars, 'g'); - property = this.variable.value.replace(regex, ''); - } - if (this.containsVariableReference) { - property = '$'; - } - } - - return property; - }, - resetValidationErrorEvents() { - this.validationErrorEventProperty = ''; - }, - }, - i18n: { - awsTipTitle: AWS_TIP_TITLE, - awsTipMessage: AWS_TIP_MESSAGE, - containsVariableReferenceMessage: CONTAINS_VARIABLE_REFERENCE_MESSAGE, - defaultScope: allEnvironments.text, - environmentScopeLinkTitle: ENVIRONMENT_SCOPE_LINK_TITLE, - expandedVariablesNote: EXPANDED_VARIABLES_NOTE, - flagsLinkTitle: FLAG_LINK_TITLE, - }, - flagLink: helpPagePath('ci/variables/index', { - anchor: 'define-a-cicd-variable-in-the-ui', - }), - oidcLink: helpPagePath('ci/cloud_services/index', { - anchor: 'oidc-authorization-with-your-cloud-provider', - }), - modalId: ADD_CI_VARIABLE_MODAL_ID, - tokens: awsTokens, - tokenList: awsTokenList, - variableOptions, -}; -</script> - -<template> - <gl-modal - ref="modal" - :modal-id="$options.modalId" - :title="modalActionText" - static - lazy - @hidden="resetModalHandler" - @shown="onShow" - > - <gl-collapse :visible="isTipVisible"> - <gl-alert - :title="$options.i18n.awsTipTitle" - variant="warning" - class="gl-mb-5" - data-testid="aws-guidance-tip" - @dismiss="dismissTip" - > - <gl-sprintf :message="$options.i18n.awsTipMessage"> - <template #link="{ content }"> - <gl-link :href="$options.oidcLink"> - {{ content }} - </gl-link> - </template> - </gl-sprintf> - </gl-alert> - </gl-collapse> - <form> - <gl-form-combobox - v-model="variable.key" - :token-list="$options.tokenList" - :label-text="__('Key')" - data-testid="pipeline-form-ci-variable-key" - data-qa-selector="ci_variable_key_field" - /> - - <gl-form-group - :label="__('Value')" - label-for="ci-variable-value" - :state="variableValidationState" - :description="variableValueHelpText" - :invalid-feedback="variableValidationFeedback" - > - <gl-form-textarea - id="ci-variable-value" - ref="valueField" - v-model="variable.value" - :state="variableValidationState" - rows="3" - max-rows="10" - data-testid="pipeline-form-ci-variable-value" - data-qa-selector="ci_variable_value_field" - class="gl-font-monospace!" - spellcheck="false" - /> - <p v-if="isRaw" class="gl-mt-2 gl-mb-0 text-secondary" data-testid="raw-variable-tip"> - {{ __('Variable value will be evaluated as raw string.') }} - </p> - </gl-form-group> - - <div class="gl-display-flex"> - <gl-form-group :label="__('Type')" label-for="ci-variable-type" class="gl-w-half gl-mr-5"> - <gl-form-select - id="ci-variable-type" - v-model="variable.variableType" - :options="$options.variableOptions" - /> - </gl-form-group> - - <template v-if="!hideEnvironmentScope"> - <gl-form-group - label-for="ci-variable-env" - class="gl-w-half" - data-testid="environment-scope" - > - <template #label> - <div class="gl-display-flex gl-align-items-center"> - <span class="gl-mr-2"> - {{ __('Environment scope') }} - </span> - <gl-link - class="gl-display-flex" - :title="$options.i18n.environmentScopeLinkTitle" - :href="environmentScopeLink" - target="_blank" - data-testid="environment-scope-link" - > - <gl-icon name="question-o" :size="14" /> - </gl-link> - </div> - </template> - <ci-environments-dropdown - v-if="areScopedVariablesAvailable" - :are-environments-loading="areEnvironmentsLoading" - :selected-environment-scope="variable.environmentScope" - :environments="environments" - @select-environment="setEnvironmentScope" - @search-environment-scope="$emit('search-environment-scope', $event)" - /> - - <gl-form-input v-else :value="$options.i18n.defaultScope" class="gl-w-full" readonly /> - </gl-form-group> - </template> - </div> - - <gl-form-group> - <template #label> - <div class="gl-display-flex gl-align-items-center"> - <span class="gl-mr-2"> - {{ __('Flags') }} - </span> - <gl-link - class="gl-display-flex" - :title="$options.i18n.flagsLinkTitle" - :href="$options.flagLink" - target="_blank" - > - <gl-icon name="question-o" :size="14" /> - </gl-link> - </div> - </template> - <gl-form-checkbox - v-model="variable.protected" - class="gl-mb-0" - data-testid="ci-variable-protected-checkbox" - :data-is-protected-checked="variable.protected" - > - {{ __('Protect variable') }} - <p class="gl-mt-2 text-secondary"> - {{ __('Export variable to pipelines running on protected branches and tags only.') }} - </p> - </gl-form-checkbox> - <gl-form-checkbox - ref="masked-ci-variable" - v-model="variable.masked" - data-testid="ci-variable-masked-checkbox" - > - {{ __('Mask variable') }} - <p class="gl-mt-2 text-secondary"> - <gl-sprintf - :message=" - __( - 'Mask this variable in job logs if it meets %{linkStart}regular expression requirements%{linkEnd}.', - ) - " - > - <template #link="{ content }" - ><gl-link target="_blank" :href="maskedEnvironmentVariablesLink">{{ - content - }}</gl-link> - </template> - </gl-sprintf> - </p> - </gl-form-checkbox> - <gl-form-checkbox - ref="expanded-ci-variable" - :checked="isExpanded" - data-testid="ci-variable-expanded-checkbox" - @change="setVariableRaw" - > - {{ __('Expand variable reference') }} - <p class="gl-mt-2 gl-mb-0 gl-text-secondary"> - <gl-sprintf :message="$options.i18n.expandedVariablesNote"> - <template #code="{ content }"> - <code>{{ content }}</code> - </template> - </gl-sprintf> - </p> - </gl-form-checkbox> - </gl-form-group> - </form> - - <gl-alert - v-if="containsVariableReference" - :title="__('Value might contain a variable reference')" - :dismissible="false" - variant="warning" - data-testid="contains-variable-reference" - > - <gl-sprintf :message="$options.i18n.containsVariableReferenceMessage"> - <template #code="{ content }"> - <code>{{ content }}</code> - </template> - <template #docsLink="{ content }"> - <gl-link :href="containsVariableReferenceLink" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </gl-alert> - <template #modal-footer> - <gl-button @click="hideModal">{{ __('Cancel') }}</gl-button> - <gl-button - v-if="isEditing" - ref="deleteCiVariable" - variant="danger" - category="secondary" - @click="deleteVarAndClose" - >{{ __('Delete variable') }}</gl-button - > - <gl-button - ref="updateOrAddVariable" - :disabled="!canSubmit" - variant="confirm" - category="primary" - data-testid="ciUpdateOrAddVariableBtn" - data-qa-selector="ci_variable_save_button" - @click="updateOrAddVariable" - >{{ modalActionText }} - </gl-button> - </template> - </gl-modal> -</template> diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue index f2d81b3f271..99270d36df7 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue @@ -3,13 +3,11 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { ADD_VARIABLE_ACTION, EDIT_VARIABLE_ACTION, VARIABLE_ACTIONS } from '../constants'; import CiVariableDrawer from './ci_variable_drawer.vue'; import CiVariableTable from './ci_variable_table.vue'; -import CiVariableModal from './ci_variable_modal.vue'; export default { components: { CiVariableDrawer, CiVariableTable, - CiVariableModal, }, mixins: [glFeatureFlagsMixin()], props: { @@ -65,15 +63,6 @@ export default { showForm() { return VARIABLE_ACTIONS.includes(this.mode); }, - useDrawerForm() { - return this.glFeatures?.ciVariableDrawer; - }, - showDrawer() { - return this.showForm && this.useDrawerForm; - }, - showModal() { - return this.showForm && !this.useDrawerForm; - }, }, methods: { addVariable(variable) { @@ -116,23 +105,8 @@ export default { @delete-variable="deleteVariable" @sort-changed="(val) => $emit('sort-changed', val)" /> - <ci-variable-modal - v-if="showModal" - :are-environments-loading="areEnvironmentsLoading" - :are-scoped-variables-available="areScopedVariablesAvailable" - :environments="environments" - :hide-environment-scope="hideEnvironmentScope" - :variables="variables" - :mode="mode" - :selected-variable="selectedVariable" - @add-variable="addVariable" - @delete-variable="deleteVariable" - @close-form="closeForm" - @update-variable="updateVariable" - @search-environment-scope="$emit('search-environment-scope', $event)" - /> <ci-variable-drawer - v-if="showDrawer" + v-if="showForm" :are-environments-loading="areEnvironmentsLoading" :are-scoped-variables-available="areScopedVariablesAvailable" :environments="environments" diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue index 3d62313815c..86287d586ec 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue @@ -16,7 +16,6 @@ import { import { __, s__, sprintf } from '~/locale'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { - ADD_CI_VARIABLE_MODAL_ID, DEFAULT_EXCEEDS_VARIABLE_LIMIT_TEXT, EXCEEDS_VARIABLE_LIMIT_TEXT, MAXIMUM_VARIABLE_LIMIT_REACHED, @@ -25,7 +24,6 @@ import { import { convertEnvironmentScope } from '../utils'; export default { - modalId: ADD_CI_VARIABLE_MODAL_ID, defaultFields: [ { key: 'key', @@ -243,10 +241,8 @@ export default { >{{ valuesButtonText }}</gl-button > <gl-button - v-gl-modal-directive="$options.modalId" size="small" :disabled="exceedsVariableLimit" - data-qa-selector="add_ci_variable_button" data-testid="add-ci-variable-button" @click="setSelectedVariable()" >{{ $options.i18n.addButton }}</gl-button @@ -375,12 +371,11 @@ export default { <template v-if="!isInheritedGroupVars" #cell(actions)="{ item }"> <div class="gl-display-flex gl-justify-content-end gl-mt-n2 gl-mb-n2"> <gl-button - v-gl-modal-directive="$options.modalId" icon="pencil" size="small" class="gl-mr-3" :aria-label="$options.i18n.editButton" - data-qa-selector="edit_ci_variable_button" + data-testid="edit-ci-variable-button" @click="setSelectedVariable(item.index)" /> <gl-button @@ -390,7 +385,6 @@ export default { icon="remove" size="small" :aria-label="$options.i18n.deleteButton" - data-qa-selector="delete_ci_variable_button" /> <gl-modal ref="modal" diff --git a/app/assets/javascripts/ci/ci_variable_list/constants.js b/app/assets/javascripts/ci/ci_variable_list/constants.js index fc37b62299d..d85827b8220 100644 --- a/app/assets/javascripts/ci/ci_variable_list/constants.js +++ b/app/assets/javascripts/ci/ci_variable_list/constants.js @@ -1,6 +1,5 @@ import { __, s__, sprintf } from '~/locale'; -export const ADD_CI_VARIABLE_MODAL_ID = 'add-ci-variable'; export const ENVIRONMENT_QUERY_LIMIT = 30; export const SORT_DIRECTIONS = { @@ -45,7 +44,6 @@ export const AWS_TIP_MESSAGE = s__( 'CiVariable|GitLab CI/CD supports OpenID Connect (OIDC) to give your build and deployment jobs access to cloud credentials and services. %{linkStart}How do I configure OIDC for my cloud provider?%{linkEnd}', ); -export const EVENT_LABEL = 'ci_variable_modal'; export const DRAWER_EVENT_LABEL = 'ci_variable_drawer'; export const EVENT_ACTION = 'validation_error'; diff --git a/app/assets/javascripts/ci/common/pipelines_table.vue b/app/assets/javascripts/ci/common/pipelines_table.vue index 13b5120654a..d63d2d1713e 100644 --- a/app/assets/javascripts/ci/common/pipelines_table.vue +++ b/app/assets/javascripts/ci/common/pipelines_table.vue @@ -13,8 +13,6 @@ import PipelineUrl from '../pipelines_page/components/pipeline_url.vue'; import PipelineStatusBadge from '../pipelines_page/components/pipeline_status_badge.vue'; const HIDE_TD_ON_MOBILE = 'gl-display-none! gl-lg-display-table-cell!'; -const DEFAULT_TH_CLASSES = - 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!'; /** * Pipelines Table @@ -77,7 +75,6 @@ export default { { key: 'status', label: s__('Pipeline|Status'), - thClass: DEFAULT_TH_CLASSES, columnClass: 'gl-w-15p', tdClass: this.tdClasses, thAttr: { 'data-testid': 'status-th' }, @@ -85,7 +82,6 @@ export default { { key: 'pipeline', label: __('Pipeline'), - thClass: DEFAULT_TH_CLASSES, tdClass: `${this.tdClasses}`, columnClass: 'gl-w-30p', thAttr: { 'data-testid': 'pipeline-th' }, @@ -93,7 +89,6 @@ export default { { key: 'triggerer', label: s__('Pipeline|Created by'), - thClass: DEFAULT_TH_CLASSES, tdClass: `${this.tdClasses} ${HIDE_TD_ON_MOBILE}`, columnClass: 'gl-w-15p', thAttr: { 'data-testid': 'triggerer-th' }, @@ -101,14 +96,12 @@ export default { { key: 'stages', label: s__('Pipeline|Stages'), - thClass: DEFAULT_TH_CLASSES, tdClass: this.tdClasses, columnClass: 'gl-w-quarter', thAttr: { 'data-testid': 'stages-th' }, }, { key: 'actions', - thClass: DEFAULT_TH_CLASSES, tdClass: this.tdClasses, columnClass: 'gl-w-20p', thAttr: { 'data-testid': 'actions-th' }, @@ -137,8 +130,7 @@ export default { return cleanLeadingSeparator(item.project.full_path); }, failedJobsCount(pipeline) { - // Remove `pipeline?.failed_builds?.length` when we remove `ci_fix_performance_pipelines_json_endpoint`. - return pipeline?.failed_builds_count || pipeline?.failed_builds?.length || 0; + return pipeline?.failed_builds_count || 0; }, onRefreshPipelinesTable() { this.$emit('refresh-pipelines-table'); diff --git a/app/assets/javascripts/ci/common/private/job_action_component.vue b/app/assets/javascripts/ci/common/private/job_action_component.vue index b0fa724d450..c266e061513 100644 --- a/app/assets/javascripts/ci/common/private/job_action_component.vue +++ b/app/assets/javascripts/ci/common/private/job_action_component.vue @@ -119,6 +119,7 @@ export default { ref="button" :class="cssClass" :disabled="isDisabled" + size="small" class="js-ci-action gl-ci-action-icon-container ci-action-icon-container ci-action-icon-wrapper gl-display-flex gl-align-items-center gl-justify-content-center" data-testid="ci-action-button" @click.stop="onClickAction" @@ -129,8 +130,17 @@ export default { class="gl-display-flex gl-align-items-center gl-justify-content-center gl-h-full" data-testid="ci-action-icon-tooltip-wrapper" > - <gl-loading-icon v-if="isLoading" size="sm" class="js-action-icon-loading" /> - <gl-icon v-else :name="actionIcon" class="gl-mr-0!" :aria-label="actionIcon" /> + <gl-loading-icon + v-if="isLoading" + size="sm" + class="gl-button-icon gl-m-2 js-action-icon-loading" + /> + <gl-icon + v-else + :name="actionIcon" + class="gl-button-icon gl-p-1 gl-mr-0!" + :aria-label="actionIcon" + /> </div> </gl-button> </template> diff --git a/app/assets/javascripts/ci/common/private/job_links_layer.vue b/app/assets/javascripts/ci/common/private/job_links_layer.vue index 59260ca3f81..9b3647e9c55 100644 --- a/app/assets/javascripts/ci/common/private/job_links_layer.vue +++ b/app/assets/javascripts/ci/common/private/job_links_layer.vue @@ -1,5 +1,6 @@ <script> import { memoize } from 'lodash'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { reportToSentry } from '~/ci/utils'; import { parseData } from '~/ci/pipeline_details/utils/parsing_utils'; import LinksInner from '~/ci/pipeline_details/graph/components/links_inner.vue'; @@ -16,6 +17,7 @@ export default { components: { LinksInner, }, + mixins: [glFeatureFlagMixin()], props: { containerMeasurements: { type: Object, @@ -50,6 +52,9 @@ export default { showLinkedLayers() { return this.showLinks && !this.containerZero; }, + isNewPipelineGraph() { + return this.glFeatures.newPipelineGraph; + }, }, errorCaptured(err, _vm, info) { reportToSentry(this.$options.name, `error: ${err}, info: ${info}`); @@ -68,7 +73,10 @@ export default { <slot></slot> </links-inner> <div v-else> - <div class="gl-display-flex gl-relative"> + <div + class="gl-display-flex gl-relative" + :class="{ 'gl-flex-wrap gl-sm-flex-nowrap': isNewPipelineGraph }" + > <slot></slot> </div> </div> diff --git a/app/assets/javascripts/ci/common/private/job_name_component.vue b/app/assets/javascripts/ci/common/private/job_name_component.vue index 1c7f5a7476d..b4e831d69d4 100644 --- a/app/assets/javascripts/ci/common/private/job_name_component.vue +++ b/app/assets/javascripts/ci/common/private/job_name_component.vue @@ -30,7 +30,7 @@ export default { </script> <template> <span class="mw-100 gl-display-flex gl-align-items-center gl-flex-grow-1"> - <ci-icon :size="iconSize" :status="status" class="gl-line-height-0" /> + <ci-icon :size="iconSize" :status="status" :show-tooltip="false" class="gl-line-height-0" /> <span class="gl-text-truncate mw-70p gl-pl-3 gl-display-inline-block"> {{ name }} </span> diff --git a/app/assets/javascripts/ci/constants.js b/app/assets/javascripts/ci/constants.js index 5b60528f521..138a44a8dd0 100644 --- a/app/assets/javascripts/ci/constants.js +++ b/app/assets/javascripts/ci/constants.js @@ -37,4 +37,5 @@ export const TRACKING_CATEGORIES = { search: 'pipelines_filtered_search', failed: 'pipeline_failed_jobs_tab', tests: 'pipeline_tests_tab', + listbox: 'pipeline_id_iid_listbox', }; diff --git a/app/assets/javascripts/ci/job_details/components/job_header.vue b/app/assets/javascripts/ci/job_details/components/job_header.vue index 00d15f87064..1aa83a94bc5 100644 --- a/app/assets/javascripts/ci/job_details/components/job_header.vue +++ b/app/assets/javascripts/ci/job_details/components/job_header.vue @@ -4,12 +4,12 @@ import SafeHtml from '~/vue_shared/directives/safe_html'; import { isGid, getIdFromGraphQLId } from '~/graphql_shared/utils'; import { glEmojiTag } from '~/emoji'; import { __, sprintf } from '~/locale'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; export default { components: { - CiBadgeLink, + CiIcon, TimeagoTooltip, GlButton, GlAvatarLink, @@ -113,7 +113,7 @@ export default { </div> </div> <section class="header-main-content gl-display-flex gl-align-items-center gl-mr-3"> - <ci-badge-link class="gl-mr-3" :status="status" /> + <ci-icon class="gl-mr-3" :status="status" show-status-text /> <template v-if="shouldRenderTriggeredLabel">{{ __('Started') }}</template> <template v-else>{{ __('Created') }}</template> diff --git a/app/assets/javascripts/ci/job_details/components/log/line_header.vue b/app/assets/javascripts/ci/job_details/components/log/line_header.vue index 658a94e6af4..d36701323da 100644 --- a/app/assets/javascripts/ci/job_details/components/log/line_header.vue +++ b/app/assets/javascripts/ci/job_details/components/log/line_header.vue @@ -17,7 +17,8 @@ export default { }, isClosed: { type: Boolean, - required: true, + required: false, + default: false, }, path: { type: String, diff --git a/app/assets/javascripts/ci/job_details/components/sidebar/job_container_item.vue b/app/assets/javascripts/ci/job_details/components/sidebar/job_container_item.vue index 8e87f118fa4..4ec9044a21c 100644 --- a/app/assets/javascripts/ci/job_details/components/sidebar/job_container_item.vue +++ b/app/assets/javascripts/ci/job_details/components/sidebar/job_container_item.vue @@ -63,11 +63,11 @@ export default { <gl-icon v-if="isActive" name="arrow-right" + :show-tooltip="false" class="icon-arrow-right gl-absolute gl-display-block" - :size="14" /> - <ci-icon :status="job.status" class="gl-mr-3" :size="14" /> + <ci-icon :status="job.status" :show-tooltip="false" class="gl-mr-3" /> <span class="gl-text-truncate gl-w-full">{{ jobName }}</span> diff --git a/app/assets/javascripts/ci/job_details/components/sidebar/stages_dropdown.vue b/app/assets/javascripts/ci/job_details/components/sidebar/stages_dropdown.vue index 7744395734f..e229abcbe12 100644 --- a/app/assets/javascripts/ci/job_details/components/sidebar/stages_dropdown.vue +++ b/app/assets/javascripts/ci/job_details/components/sidebar/stages_dropdown.vue @@ -1,7 +1,7 @@ <script> import { GlLink, GlDisclosureDropdown, GlSprintf } from '@gitlab/ui'; import { isEmpty } from 'lodash'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { Mousetrap } from '~/lib/mousetrap'; import { s__ } from '~/locale'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; @@ -14,7 +14,7 @@ export default { GlDisclosureDropdown, GlLink, GlSprintf, - CiBadgeLink, + CiIcon, }, props: { pipeline: { @@ -94,7 +94,10 @@ export default { </script> <template> <div class="dropdown"> - <div class="gl-display-flex gl-flex-wrap gl-gap-2 js-pipeline-info" data-testid="pipeline-info"> + <div + class="gl-display-flex gl-flex-wrap gl-align-items-center gl-gap-2 js-pipeline-info" + data-testid="pipeline-info" + > <gl-sprintf :message="pipelineInfo"> <template #bold="{ content }"> <span class="gl-display-flex gl-font-weight-bold">{{ content }}</span> @@ -108,9 +111,9 @@ export default { > </template> <template #status> - <ci-badge-link + <ci-icon :status="pipeline.details.status" - size="sm" + show-status-text data-testid="pipeline-status-link" /> </template> @@ -125,7 +128,7 @@ export default { <template #ref> <gl-link :href="pipeline.ref.path" - class="link-commit ref-name gl-mt-1" + class="link-commit ref-name" data-testid="source-ref-link" >{{ pipeline.ref.name }}</gl-link ><clipboard-button diff --git a/app/assets/javascripts/ci/job_details/job_app.vue b/app/assets/javascripts/ci/job_details/job_app.vue index 119f8259be7..e0708289b43 100644 --- a/app/assets/javascripts/ci/job_details/job_app.vue +++ b/app/assets/javascripts/ci/job_details/job_app.vue @@ -307,7 +307,7 @@ export default { @scrollJobLogBottom="scrollBottom" @searchResults="setSearchResults" /> - <log :job-log="jobLog" :is-complete="isJobLogComplete" :search-results="searchResults" /> + <log :search-results="searchResults" /> </div> <!-- EO job log --> diff --git a/app/assets/javascripts/ci/job_details/store/actions.js b/app/assets/javascripts/ci/job_details/store/actions.js index fa23589f7d6..6f538e3b3d4 100644 --- a/app/assets/javascripts/ci/job_details/store/actions.js +++ b/app/assets/javascripts/ci/job_details/store/actions.js @@ -175,7 +175,7 @@ export const fetchJobLog = ({ dispatch, state }) => } }) .catch((e) => { - if (e.response.status === HTTP_STATUS_FORBIDDEN) { + if (e.response?.status === HTTP_STATUS_FORBIDDEN) { dispatch('receiveJobLogUnauthorizedError'); } else { reportToSentry('job_actions', e); diff --git a/app/assets/javascripts/ci/job_details/store/utils.js b/app/assets/javascripts/ci/job_details/store/utils.js index b18a3fa162d..c8b33638821 100644 --- a/app/assets/javascripts/ci/job_details/store/utils.js +++ b/app/assets/javascripts/ci/job_details/store/utils.js @@ -117,28 +117,31 @@ export const getNextLineNumber = (acc) => { * @returns Array parsed log lines */ export const logLinesParser = (lines = [], prevLogLines = [], hash = '') => - lines.reduce((acc, line) => { - const lineNumber = getNextLineNumber(acc); - - const last = acc[acc.length - 1]; - - // If the object is an header, we parse it into another structure - if (line.section_header) { - acc.push(parseHeaderLine(line, lineNumber, hash)); - } else if (isCollapsibleSection(acc, last, line)) { - // if the object belongs to a nested section, we append it to the new `lines` array of the - // previously formatted header - last.lines.push(parseLine(line, lineNumber)); - } else if (line.section_duration) { - // if the line has section_duration, we look for the correct header to add it - addDurationToHeader(acc, line); - } else { - // otherwise it's a regular line - acc.push(parseLine(line, lineNumber)); - } + lines.reduce( + (acc, line) => { + const lineNumber = getNextLineNumber(acc); + + const last = acc[acc.length - 1]; + + // If the object is an header, we parse it into another structure + if (line.section_header) { + acc.push(parseHeaderLine(line, lineNumber, hash)); + } else if (isCollapsibleSection(acc, last, line)) { + // if the object belongs to a nested section, we append it to the new `lines` array of the + // previously formatted header + last.lines.push(parseLine(line, lineNumber)); + } else if (line.section_duration) { + // if the line has section_duration, we look for the correct header to add it + addDurationToHeader(acc, line); + } else { + // otherwise it's a regular line + acc.push(parseLine(line, lineNumber)); + } - return acc; - }, prevLogLines); + return acc; + }, + [...prevLogLines], + ); /** * Finds the repeated offset, removes the old one diff --git a/app/assets/javascripts/ci/jobs_page/components/job_cells/job_cell.vue b/app/assets/javascripts/ci/jobs_page/components/job_cells/job_cell.vue index fbdfc7c9c6a..b97243cf2ca 100644 --- a/app/assets/javascripts/ci/jobs_page/components/job_cells/job_cell.vue +++ b/app/assets/javascripts/ci/jobs_page/components/job_cells/job_cell.vue @@ -136,8 +136,8 @@ export default { v-if="triggered" variant="info" :size="$options.badgeSize" - data-testid="triggered-job-badge" - >{{ s__('Job|triggered') }} + data-testid="trigger-token-job-badge" + >{{ s__('Job|trigger token') }} </gl-badge> <gl-badge v-if="showAllowedToFailBadge" diff --git a/app/assets/javascripts/ci/jobs_page/components/job_cells/status_cell.vue b/app/assets/javascripts/ci/jobs_page/components/job_cells/status_cell.vue index a2b6a430138..efa74d86bd6 100644 --- a/app/assets/javascripts/ci/jobs_page/components/job_cells/status_cell.vue +++ b/app/assets/javascripts/ci/jobs_page/components/job_cells/status_cell.vue @@ -1,14 +1,14 @@ <script> import { GlIcon } from '@gitlab/ui'; import { formatTime } from '~/lib/utils/datetime_utility'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; export default { iconSize: 12, components: { - CiBadgeLink, + CiIcon, GlIcon, TimeAgoTooltip, }, @@ -38,7 +38,7 @@ export default { <template> <div> - <ci-badge-link :status="job.detailedStatus" /> + <ci-icon :status="job.detailedStatus" show-status-text /> <div class="gl-font-sm gl-text-secondary gl-mt-2 gl-ml-3"> <div v-if="duration" data-testid="job-duration"> <gl-icon name="timer" :size="$options.iconSize" data-testid="duration-icon" /> diff --git a/app/assets/javascripts/ci/jobs_page/jobs_page_app.vue b/app/assets/javascripts/ci/jobs_page/jobs_page_app.vue index 03e0f2dadc8..09bbb7afbca 100644 --- a/app/assets/javascripts/ci/jobs_page/jobs_page_app.vue +++ b/app/assets/javascripts/ci/jobs_page/jobs_page_app.vue @@ -58,9 +58,6 @@ export default { }, jobsCount: { query: GetJobsCount, - context: { - isSingleRequest: true, - }, variables() { return { fullPath: this.fullPath, diff --git a/app/assets/javascripts/ci/pipeline_details/constants.js b/app/assets/javascripts/ci/pipeline_details/constants.js index 70b758ae6b0..51d0e980e78 100644 --- a/app/assets/javascripts/ci/pipeline_details/constants.js +++ b/app/assets/javascripts/ci/pipeline_details/constants.js @@ -2,7 +2,6 @@ import { __, s__ } from '~/locale'; export const CANCEL_REQUEST = 'CANCEL_REQUEST'; export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref', 'status', 'source']; -export const SCHEDULE_ORIGIN = 'schedule'; export const NEEDS_PROPERTY = 'needs'; export const EXPLICIT_NEEDS_PROPERTY = 'previousStageJobsOrNeeds'; diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue index f098d790736..3da2f27c1b9 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue @@ -4,6 +4,7 @@ import { generateColumnsFromLayersListMemoized, keepLatestDownstreamPipelines, } from '~/ci/pipeline_details/utils/parsing_utils'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import LinksLayer from '../../../common/private/job_links_layer.vue'; import { DOWNSTREAM, MAIN, UPSTREAM, ONE_COL_WIDTH, STAGE_VIEW } from '../constants'; import { validateConfigPaths } from '../utils'; @@ -19,6 +20,7 @@ export default { LinkedPipelinesColumn, StageColumnComponent, }, + mixins: [glFeatureFlagMixin()], props: { configPaths: { type: Object, @@ -132,6 +134,9 @@ export default { upstreamPipelines() { return this.hasUpstreamPipelines ? this.pipeline.upstream : []; }, + isNewPipelineGraph() { + return this.glFeatures.newPipelineGraph; + }, }, errorCaptured(err, _vm, info) { reportToSentry(this.$options.name, `error: ${err}, info: ${info}`); @@ -178,10 +183,15 @@ export default { <div class="js-pipeline-graph"> <div ref="mainPipelineContainer" - class="gl-display-flex gl-position-relative gl-bg-gray-10 gl-white-space-nowrap" + class="pipeline-graph gl-display-flex gl-position-relative gl-white-space-nowrap gl-rounded-lg" :class="{ - 'gl-pipeline-min-h gl-py-5 gl-overflow-auto': !isLinkedPipeline, + 'gl-bg-gray-10': !isNewPipelineGraph, + 'gl-pipeline-min-h gl-py-5 gl-overflow-auto': !isNewPipelineGraph && !isLinkedPipeline, + 'pipeline-graph-container gl-bg-gray-10 gl-pipeline-min-h gl-align-items-flex-start gl-pt-3 gl-pb-8 gl-mt-3 gl-overflow-auto': + isNewPipelineGraph && !isLinkedPipeline, + 'gl-bg-gray-50 gl-sm-ml-5': isNewPipelineGraph && isLinkedPipeline, }" + data-testid="pipeline-container" > <linked-graph-wrapper> <template #upstream> @@ -199,7 +209,7 @@ export default { /> </template> <template #main> - <div :id="containerId" :ref="containerId"> + <div :id="containerId" :ref="containerId" class="pipeline-links-container"> <links-layer :pipeline-data="layout" :pipeline-id="pipeline.id" @@ -238,7 +248,7 @@ export default { <template #downstream> <linked-pipelines-column v-if="showDownstreamPipelines" - class="gl-mr-6" + :class="{ 'gl-sm-ml-3': isNewPipelineGraph }" :config-paths="configPaths" :linked-pipelines="downstreamPipelines" :column-title="__('Downstream')" diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_view_selector.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_view_selector.vue index fb7dcb300f1..114b224fbe7 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_view_selector.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_view_selector.vue @@ -1,11 +1,11 @@ <script> import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon, GlToggle } from '@gitlab/ui'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { __, s__ } from '~/locale'; import { STAGE_VIEW, LAYER_VIEW } from '../constants'; export default { name: 'GraphViewSelector', - components: { GlAlert, GlButton, @@ -13,7 +13,7 @@ export default { GlLoadingIcon, GlToggle, }, - + mixins: [glFeatureFlagMixin()], props: { showLinks: { type: Boolean, @@ -77,6 +77,9 @@ export default { }; }); }, + isNewPipelineGraph() { + return this.glFeatures.newPipelineGraph; + }, }, watch: { /* @@ -138,7 +141,13 @@ export default { <template> <div> - <div class="gl-relative gl-display-flex gl-align-items-center gl-w-max-content gl-my-4"> + <div + class="gl-relative gl-display-flex gl-align-items-center gl-my-4" + :class="{ + 'gl-w-max-content': !isNewPipelineGraph, + 'gl-flex-wrap gl-sm-flex-nowrap': isNewPipelineGraph, + }" + > <gl-loading-icon v-if="isSwitcherLoading" data-testid="switcher-loading-state" @@ -161,7 +170,10 @@ export default { <gl-toggle v-model="showLinksActive" data-testid="show-links-toggle" - class="gl-mx-4" + :class="{ + 'gl-mx-4': !isNewPipelineGraph, + 'gl-sm-ml-4 gl-mt-4 gl-sm-mt-0': isNewPipelineGraph, + }" :label="$options.i18n.linksLabelText" :is-loading="isToggleLoading" label-position="left" diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/job_item.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/job_item.vue index bb36ac8b6ab..c6340e6787a 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/job_item.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/job_item.vue @@ -5,7 +5,7 @@ import delayedJobMixin from '~/ci/mixins/delayed_job_mixin'; import { helpPagePath } from '~/helpers/help_page_helper'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import { __, s__, sprintf } from '~/locale'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import ActionComponent from '../../../common/private/job_action_component.vue'; import JobNameComponent from '../../../common/private/job_name_component.vue'; import { BRIDGE_KIND, RETRY_ACTION_TITLE, SINGLE_JOB, SKIP_RETRY_MODAL_KEY } from '../constants'; @@ -58,7 +58,7 @@ export default { hoverClass: 'gl-shadow-x0-y0-b3-s1-blue-500', components: { ActionComponent, - CiBadgeLink, + CiIcon, GlBadge, GlForm, GlFormCheckbox, @@ -329,7 +329,7 @@ export default { @mouseout="hideTooltips" > <div class="gl-display-flex gl-align-items-center gl-flex-grow-1"> - <ci-badge-link :status="job.status" size="md" :show-text="false" :use-link="false" /> + <ci-icon :status="job.status" :use-link="false" /> <div class="gl-pl-3 gl-pr-3 gl-display-flex gl-flex-direction-column gl-pipeline-job-width"> <div class="gl-text-truncate gl-pr-9 gl-line-height-normal">{{ job.name }}</div> <div diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue index fb2280d971a..0d72373a0f5 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue @@ -1,5 +1,20 @@ +<script> +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; + +export default { + mixins: [glFeatureFlagMixin()], + computed: { + isNewPipelineGraph() { + return this.glFeatures.newPipelineGraph; + }, + }, +}; +</script> <template> - <div class="gl-display-flex"> + <div + class="gl-display-flex" + :class="{ 'gl-flex-wrap gl-sm-flex-nowrap gl-w-full': isNewPipelineGraph }" + > <slot name="upstream"></slot> <slot name="main"></slot> <slot name="downstream"></slot> diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipeline.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipeline.vue index 5960eea5b4f..26521f87426 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipeline.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipeline.vue @@ -7,13 +7,14 @@ import { GlTooltip, GlTooltipDirective, } from '@gitlab/ui'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { TYPENAME_CI_PIPELINE } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import { __, sprintf } from '~/locale'; import CancelPipelineMutation from '~/ci/pipeline_details/graphql/mutations/cancel_pipeline.mutation.graphql'; import RetryPipelineMutation from '~/ci/pipeline_details/graphql/mutations/retry_pipeline.mutation.graphql'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { reportToSentry } from '~/ci/utils'; import { ACTION_FAILURE, DOWNSTREAM, UPSTREAM } from '../constants'; @@ -22,13 +23,14 @@ export default { GlTooltip: GlTooltipDirective, }, components: { - CiBadgeLink, + CiIcon, GlBadge, GlButton, GlLink, GlLoadingIcon, GlTooltip, }, + mixins: [glFeatureFlagMixin()], styles: { actionSizeClasses: ['gl-h-7 gl-w-7'], flatLeftBorder: ['gl-rounded-bottom-left-none!', 'gl-rounded-top-left-none!'], @@ -115,9 +117,6 @@ export default { downstreamTitle() { return this.childPipeline ? this.sourceJobName : this.pipeline.project.name; }, - flexDirection() { - return this.isUpstream ? 'gl-flex-direction-row-reverse' : 'gl-flex-direction-row'; - }, graphqlPipelineId() { return convertToGraphQLId(TYPENAME_CI_PIPELINE, this.pipeline.id); }, @@ -176,6 +175,9 @@ export default { return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} - ${this.sourceJobInfo}`; }, + isNewPipelineGraph() { + return this.glFeatures.newPipelineGraph; + }, }, errorCaptured(err, _vm, info) { reportToSentry('linked_pipeline', `error: ${err}, info: ${info}`); @@ -231,9 +233,15 @@ export default { <template> <div ref="linkedPipeline" - class="gl-h-full gl-display-flex! gl-px-2" - :class="flexDirection" + class="linked-pipeline-container gl-h-full gl-display-flex!" + :class="{ + 'gl-flex-direction-row-reverse': isUpstream, + 'gl-flex-direction-row': !isUpstream, + 'gl-px-2': !isNewPipelineGraph, + 'gl-w-full gl-sm-w-auto': isNewPipelineGraph, + }" data-testid="linked-pipeline-container" + :aria-expanded="expanded" @mouseover="onDownstreamHovered" @mouseleave="onDownstreamHoverLeave" > @@ -242,17 +250,15 @@ export default { </gl-tooltip> <div class="gl-bg-white gl-border gl-p-3 gl-rounded-lg gl-w-full" :class="cardClasses"> <div class="gl-display-flex gl-gap-x-3"> - <ci-badge-link + <ci-icon v-if="!pipelineIsLoading" :status="pipelineStatus" - size="md" - :show-text="false" :use-link="false" class="gl-align-self-start" /> <div v-else class="gl-pr-3"><gl-loading-icon size="sm" inline /></div> <div - class="gl-display-flex gl-downstream-pipeline-job-width gl-flex-direction-column gl-line-height-normal" + class="gl-display-flex gl-flex-direction-column gl-line-height-normal gl-downstream-pipeline-job-width" > <span class="gl-text-truncate" data-testid="downstream-title-content"> {{ downstreamTitle }} diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue index 2de7e43c9b1..395770826d8 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue @@ -1,4 +1,5 @@ <script> +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql'; import { reportToSentry } from '~/ci/utils'; import { LOAD_FAILURE } from '../../constants'; @@ -18,6 +19,7 @@ export default { LinkedPipeline, PipelineGraph: () => import('./graph_component.vue'), }, + mixins: [glFeatureFlagMixin()], props: { columnTitle: { type: String, @@ -63,23 +65,30 @@ export default { 'gl-pipeline-job-width', 'gl-text-truncate', 'gl-line-height-36', - 'gl-pl-3', - 'gl-mb-5', ], minWidth: `${ONE_COL_WIDTH}px`, computed: { columnClass() { - const positionValues = { + const positionValuesOld = { right: 'gl-ml-6', left: 'gl-mx-6', }; + const positionValues = { + right: 'gl-mx-5', + left: 'gl-mx-4 gl-flex-basis-full', + }; + const usePositionValues = this.isNewPipelineGraph ? positionValues : positionValuesOld; - return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`; + return `graph-position-${this.graphPosition} ${usePositionValues[this.graphPosition]}`; }, computedTitleClasses() { const positionalClasses = this.isUpstream ? ['gl-w-full', 'gl-linked-pipeline-padding'] : []; - return [...this.$options.titleClasses, ...positionalClasses]; + return [ + ...this.$options.titleClasses, + !this.isNewPipelineGraph ?? ['gl-pl-3', 'gl-mb-5'], + ...positionalClasses, + ]; }, graphPosition() { return this.isUpstream ? 'left' : 'right'; @@ -93,6 +102,9 @@ export default { minWidth() { return this.isUpstream ? 0 : this.$options.minWidth; }, + isNewPipelineGraph() { + return this.glFeatures.newPipelineGraph; + }, }, methods: { getPipelineData(pipeline) { @@ -197,7 +209,7 @@ export default { </script> <template> - <div class="gl-display-flex"> + <div class="gl-display-flex" :class="{ 'gl-w-full gl-sm-w-auto': isNewPipelineGraph }"> <div :class="columnClass" class="linked-pipelines-column"> <div data-testid="linked-column-title" :class="computedTitleClasses"> {{ columnTitle }} @@ -206,8 +218,12 @@ export default { <li v-for="pipeline in linkedPipelines" :key="pipeline.id" - class="gl-display-flex gl-mb-3" - :class="{ 'gl-flex-direction-row-reverse': isUpstream }" + class="gl-display-flex" + :class="{ + 'gl-mb-3': !isNewPipelineGraph, + 'gl-flex-wrap gl-sm-flex-nowrap gl-mb-6': isNewPipelineGraph, + 'gl-flex-direction-row-reverse': !isNewPipelineGraph && isUpstream, + }" > <linked-pipeline class="gl-display-inline-block" @@ -224,12 +240,15 @@ export default { <div v-if="showContainer(pipeline.id)" :style="{ minWidth }" - class="gl-display-inline-block" + class="gl-display-inline-block pipeline-show-container" > <pipeline-graph v-if="isExpanded(pipeline.id)" :type="type" - class="gl-inline-block gl-mt-n2" + class="gl-inline-block" + :class="{ + 'gl-mt-n2': !isNewPipelineGraph, + }" :config-paths="configPaths" :pipeline="currentPipeline" :computed-pipeline-info="getPipelineLayers(pipeline.id)" diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/root_graph_layout.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/root_graph_layout.vue index bcd7705669e..7c07591d0de 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/root_graph_layout.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/root_graph_layout.vue @@ -1,5 +1,12 @@ <script> +import { GlCard } from '@gitlab/ui'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; + export default { + components: { + GlCard, + }, + mixins: [glFeatureFlagMixin()], props: { stageClasses: { type: String, @@ -12,18 +19,37 @@ export default { default: '', }, }, + computed: { + isNewPipelineGraph() { + return this.glFeatures.newPipelineGraph; + }, + }, }; </script> <template> <div> - <div class="gl-display-flex gl-align-items-center gl-w-full gl-mb-5" :class="stageClasses"> - <slot name="stages"> </slot> - </div> - <div - class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-w-full" - :class="jobClasses" + <gl-card + v-if="isNewPipelineGraph" + class="gl-rounded-lg" + header-class="gl-rounded-lg gl-px-0 gl-py-0 gl-bg-white gl-border-b-0" + body-class="gl-pt-2 gl-pb-0 gl-px-2" > - <slot name="jobs"> </slot> - </div> + <template #header> + <slot name="stages"></slot> + </template> + + <slot name="jobs"></slot> + </gl-card> + <template v-else> + <div class="gl-display-flex gl-align-items-center gl-w-full" :class="stageClasses"> + <slot name="stages"> </slot> + </div> + <div + class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-w-full" + :class="jobClasses" + > + <slot name="jobs"> </slot> + </div> + </template> </div> </template> diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue index 6030adc96ad..01a9c6d030d 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue @@ -68,7 +68,7 @@ export default { required: true, }, }, - jobClasses: [ + legacyJobClasses: [ 'gl-p-3', 'gl-border-gray-100', 'gl-border-solid', @@ -82,18 +82,43 @@ export default { 'gl-hover-border-gray-200', 'gl-focus-border-gray-200', ], - titleClasses: [ + jobClasses: [ + 'gl-p-3', + 'gl-border-0', + 'gl-bg-transparent', + 'gl-rounded-base', + 'gl-hover-bg-gray-50', + 'gl-focus-bg-gray-50', + 'gl-hover-text-gray-900', + 'gl-focus-text-gray-900', + ], + legacyTitleClasses: [ 'gl-font-weight-bold', 'gl-pipeline-job-width', 'gl-text-truncate', 'gl-line-height-36', 'gl-pl-3', ], + titleClasses: [ + 'gl-font-weight-bold', + 'gl-pipeline-job-width', + 'gl-text-truncate', + 'gl-line-height-36', + 'gl-pl-4', + 'gl-mb-n2', + ], computed: { canUpdatePipeline() { return this.userPermissions.updatePipeline; }, columnSpacingClass() { + if (this.isNewPipelineGraph) { + const baseClasses = 'stage-column gl-relative gl-flex-basis-full'; + return this.isStageView + ? `${baseClasses} is-stage-view gl-m-5` + : `${baseClasses} gl-my-5 gl-mx-7`; + } + return this.isStageView ? 'gl-px-6' : 'gl-px-9'; }, hasAction() { @@ -102,6 +127,17 @@ export default { showStageName() { return !this.isStageView; }, + isNewPipelineGraph() { + return this.glFeatures.newPipelineGraph; + }, + jobClasses() { + return this.isNewPipelineGraph ? this.$options.jobClasses : this.$options.legacyJobClasses; + }, + titleClasses() { + return this.isNewPipelineGraph + ? this.$options.titleClasses + : this.$options.legacyTitleClasses; + }, }, errorCaptured(err, _vm, info) { reportToSentry('stage_column_component', `error: ${err}, info: ${info}`); @@ -135,12 +171,16 @@ export default { }; </script> <template> - <root-graph-layout :class="columnSpacingClass" data-testid="stage-column"> + <root-graph-layout + :class="columnSpacingClass" + class="stage-column gl-relative gl-flex-basis-full" + data-testid="stage-column" + > <template #stages> <div data-testid="stage-column-title" - class="gl-display-flex gl-justify-content-space-between gl-relative" - :class="$options.titleClasses" + class="stage-column-title gl-display-flex gl-justify-content-space-between gl-relative" + :class="titleClasses" > <span :title="name" class="gl-text-truncate gl-pr-3 gl-w-85p"> {{ name }} @@ -161,7 +201,11 @@ export default { :id="groupId(group)" :key="getGroupId(group)" data-testid="stage-column-group" - class="gl-relative gl-mb-3 gl-white-space-normal gl-pipeline-job-width" + class="gl-relative gl-white-space-normal gl-pipeline-job-width" + :class="{ + 'gl-mb-3': !isNewPipelineGraph, + 'gl-mb-2': isNewPipelineGraph, + }" @mouseenter="$emit('jobHover', group.name)" @mouseleave="$emit('jobHover', '')" > @@ -174,7 +218,7 @@ export default { :pipeline-expanded="pipelineExpanded" :pipeline-id="pipelineId" :stage-name="showStageName ? group.stageName : ''" - :css-class-job-name="$options.jobClasses" + :css-class-job-name="jobClasses" :class="[ { 'gl-opacity-3': isFadedOut(group.name) }, 'gl-transition-duration-slow gl-transition-timing-function-ease', @@ -188,7 +232,7 @@ export default { :group="group" :stage-name="showStageName ? group.stageName : ''" :pipeline-id="pipelineId" - :css-class-job-name="$options.jobClasses" + :css-class-job-name="jobClasses" /> </div> </div> diff --git a/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue b/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue index 51a68f6619a..651662d6395 100644 --- a/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue +++ b/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue @@ -17,7 +17,7 @@ import { setUrlFragment, redirectTo } from '~/lib/utils/url_utility'; // eslint- import { __, s__, sprintf, formatNumber } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { LOAD_FAILURE, POST_FAILURE, DELETE_FAILURE, DEFAULT } from '../constants'; @@ -38,7 +38,7 @@ export default { pipelineRetry: 'pipelineRetry', finishedStatuses: ['FAILED', 'SUCCESS', 'CANCELED'], components: { - CiBadgeLink, + CiIcon, ClipboardButton, GlAlert, GlBadge, @@ -58,13 +58,17 @@ export default { i18n: { scheduleBadgeText: s__('Pipelines|Scheduled'), scheduleBadgeTooltip: __('This pipeline was created by a schedule'), + triggerBadgeText: __('trigger token'), + triggerBadgeTooltip: __( + 'This pipeline was created by an API call authenticated with a trigger token', + ), childBadgeText: s__('Pipelines|Child pipeline (%{linkStart}parent%{linkEnd})'), childBadgeTooltip: __('This is a child pipeline within the parent pipeline'), latestBadgeText: s__('Pipelines|latest'), latestBadgeTooltip: __('Latest pipeline for the most recent commit on this branch'), mergeTrainBadgeText: s__('Pipelines|merge train'), mergeTrainBadgeTooltip: s__( - 'Pipelines|This pipeline ran on the contents of this merge request combined with the contents of all other merge requests queued for merging into the target branch.', + 'Pipelines|This pipeline ran on the contents of the merge request combined with the contents of all other merge requests queued for merging into the target branch.', ), invalidBadgeText: s__('Pipelines|yaml invalid'), failedBadgeText: s__('Pipelines|error'), @@ -74,7 +78,11 @@ export default { ), detachedBadgeText: s__('Pipelines|merge request'), detachedBadgeTooltip: s__( - "Pipelines|This pipeline ran on the contents of this merge request's source branch, not the target branch.", + "Pipelines|This pipeline ran on the contents of the merge request's source branch, not the target branch.", + ), + mergedResultsBadgeText: s__('Pipelines|merged results'), + mergedResultsBadgeTooltip: s__( + 'Pipelines|This pipeline ran on the contents of the merge request combined with the contents of the target branch.', ), stuckBadgeText: s__('Pipelines|stuck'), stuckBadgeTooltip: s__('Pipelines|This pipeline is stuck'), @@ -403,7 +411,7 @@ export default { {{ commitTitle }} </h3> <div> - <ci-badge-link :status="detailedStatus" class="gl-display-inline-block gl-mb-3" /> + <ci-icon :status="detailedStatus" show-status-text :show-link="false" class="gl-mb-3" /> <div class="gl-ml-2 gl-mb-3 gl-display-inline-block gl-h-6"> <gl-link v-if="user" @@ -458,6 +466,15 @@ export default { {{ $options.i18n.scheduleBadgeText }} </gl-badge> <gl-badge + v-if="badges.trigger" + v-gl-tooltip + :title="$options.i18n.triggerBadgeTooltip" + variant="info" + size="sm" + > + {{ $options.i18n.triggerBadgeText }} + </gl-badge> + <gl-badge v-if="badges.child" v-gl-tooltip :title="$options.i18n.childBadgeTooltip" @@ -527,6 +544,15 @@ export default { {{ $options.i18n.detachedBadgeText }} </gl-badge> <gl-badge + v-if="badges.mergedResultsPipeline" + v-gl-tooltip + :title="$options.i18n.mergedResultsBadgeTooltip" + variant="info" + size="sm" + > + {{ $options.i18n.mergedResultsBadgeText }} + </gl-badge> + <gl-badge v-if="badges.stuck" v-gl-tooltip :title="$options.i18n.stuckBadgeTooltip" diff --git a/app/assets/javascripts/ci/pipeline_details/jobs/components/failed_jobs_table.vue b/app/assets/javascripts/ci/pipeline_details/jobs/components/failed_jobs_table.vue index 4752fbb3e96..287f6e045c6 100644 --- a/app/assets/javascripts/ci/pipeline_details/jobs/components/failed_jobs_table.vue +++ b/app/assets/javascripts/ci/pipeline_details/jobs/components/failed_jobs_table.vue @@ -5,7 +5,7 @@ import { __, s__ } from '~/locale'; import { createAlert } from '~/alert'; import Tracking from '~/tracking'; import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { TRACKING_CATEGORIES } from '~/ci/constants'; import RetryFailedJobMutation from '../graphql/mutations/retry_failed_job.mutation.graphql'; import { DEFAULT_FIELDS } from '../../constants'; @@ -14,7 +14,7 @@ export default { fields: DEFAULT_FIELDS, retry: __('Retry'), components: { - CiBadgeLink, + CiIcon, GlButton, GlLink, GlTableLite, @@ -80,7 +80,7 @@ export default { <div class="gl-display-flex gl-align-items-center gl-lg-justify-content-start gl-justify-content-end" > - <ci-badge-link :status="item.detailedStatus" :show-text="false" class="gl-mr-3" /> + <ci-icon :status="item.detailedStatus" class="gl-mr-3" /> <div class="gl-text-truncate"> <gl-link :href="item.detailedStatus.detailsPath" diff --git a/app/assets/javascripts/ci/pipeline_details/pipeline_details_header.js b/app/assets/javascripts/ci/pipeline_details/pipeline_details_header.js index 067ec3f305e..4966b657887 100644 --- a/app/assets/javascripts/ci/pipeline_details/pipeline_details_header.js +++ b/app/assets/javascripts/ci/pipeline_details/pipeline_details_header.js @@ -23,9 +23,11 @@ export const createPipelineDetailsHeaderApp = (elSelector, apolloProvider, graph failureReason, triggeredByPath, schedule, + trigger, child, latest, mergeTrainPipeline, + mergedResultsPipeline, invalid, failed, autoDevops, @@ -59,9 +61,11 @@ export const createPipelineDetailsHeaderApp = (elSelector, apolloProvider, graph refText, badges: { schedule: parseBoolean(schedule), + trigger: parseBoolean(trigger), child: parseBoolean(child), latest: parseBoolean(latest), mergeTrainPipeline: parseBoolean(mergeTrainPipeline), + mergedResultsPipeline: parseBoolean(mergedResultsPipeline), invalid: parseBoolean(invalid), failed: parseBoolean(failed), autoDevops: parseBoolean(autoDevops), diff --git a/app/assets/javascripts/ci/pipeline_details/pipeline_shared_client.js b/app/assets/javascripts/ci/pipeline_details/pipeline_shared_client.js index c3be487caae..63a46d81dd5 100644 --- a/app/assets/javascripts/ci/pipeline_details/pipeline_shared_client.js +++ b/app/assets/javascripts/ci/pipeline_details/pipeline_shared_client.js @@ -2,10 +2,5 @@ import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; export const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient( - {}, - { - useGet: true, - }, - ), + defaultClient: createDefaultClient(), }); diff --git a/app/assets/javascripts/ci/pipeline_details/pipelines_index.js b/app/assets/javascripts/ci/pipeline_details/pipelines_index.js index 8a7c3367fc1..ea2875713a9 100644 --- a/app/assets/javascripts/ci/pipeline_details/pipelines_index.js +++ b/app/assets/javascripts/ci/pipeline_details/pipelines_index.js @@ -42,8 +42,6 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => { projectId, defaultBranchName, params, - iosRunnersAvailable, - registrationToken, fullPath, visibilityPipelineIdType, } = el.dataset; @@ -55,7 +53,6 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => { artifactsEndpoint, artifactsEndpointPlaceholder, fullPath, - iosRunnersAvailable: parseBoolean(iosRunnersAvailable), manualActionsLimit: 50, pipelineEditorPath, pipelineSchedulesPath, @@ -84,7 +81,6 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => { newPipelinePath, params: JSON.parse(params), projectId, - registrationToken, resetCachePath, store: this.store, }, diff --git a/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue b/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue index 8f4d566e7e6..204eaf20664 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue @@ -80,7 +80,7 @@ export default { <template> <div - class="gl-display-flex gl-p-3 gl-gap-3 gl-border-solid gl-border-gray-100 gl-border-1 gl-sm-flex-direction-column" + class="gl-display-flex gl-p-3 gl-gap-3 gl-border-solid gl-border-gray-100 gl-border-1 gl-flex-direction-column gl-md-flex-direction-row" > <slot></slot> <gl-button diff --git a/app/assets/javascripts/ci/pipeline_editor/components/file_nav/branch_switcher.vue b/app/assets/javascripts/ci/pipeline_editor/components/file_nav/branch_switcher.vue index 221a45d4d9a..21e21d54758 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/file_nav/branch_switcher.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/file_nav/branch_switcher.vue @@ -1,13 +1,5 @@ <script> -import { - GlDropdown, - GlDropdownItem, - GlDropdownSectionHeader, - GlInfiniteScroll, - GlLoadingIcon, - GlSearchBoxByType, - GlTooltipDirective, -} from '@gitlab/ui'; +import { GlCollapsibleListbox, GlTooltipDirective } from '@gitlab/ui'; import { produce } from 'immer'; import { historyPushState } from '~/lib/utils/common_utils'; import { setUrlParams } from '~/lib/utils/url_utility'; @@ -25,17 +17,11 @@ import getLastCommitBranch from '~/ci/pipeline_editor/graphql/queries/client/las export default { i18n: { dropdownHeader: __('Switch branch'), - title: __('Branches'), fetchError: __('Unable to fetch branch list for this project.'), }, inputDebounce: BRANCH_SEARCH_DEBOUNCE, components: { - GlDropdown, - GlDropdownItem, - GlDropdownSectionHeader, - GlInfiniteScroll, - GlLoadingIcon, - GlSearchBoxByType, + GlCollapsibleListbox, }, directives: { GlTooltip: GlTooltipDirective, @@ -66,6 +52,7 @@ export default { pageCounter: 0, searchTerm: '', lastCommitBranch: '', + infiniteScrollLoading: false, }; }, apollo: { @@ -112,6 +99,18 @@ export default { }, }, computed: { + infiniteScrollEnabled() { + return this.availableBranches.length > 0; + }, + branchesData() { + return this.availableBranches.map((branch) => ({ + text: branch, + extraAttrs: { + 'data-qa-selector': 'branch_menu_item_button', + }, + value: branch, + })); + }, availableBranchesVariables() { if (this.searchTerm.length > 0) { return { @@ -128,7 +127,7 @@ export default { enableBranchSwitcher() { return this.availableBranches.length > 0 || this.searchTerm.length > 0; }, - isBranchesLoading() { + areBranchesLoading() { return this.$apollo.queries.availableBranches.loading; }, }, @@ -143,7 +142,7 @@ export default { // if there is no searchPattern, paginate by {paginationLimit} branches fetchNextBranches() { if ( - this.isBranchesLoading || + this.areBranchesLoading || this.searchTerm.length > 0 || this.availableBranches.length >= this.totalBranches ) { @@ -178,16 +177,14 @@ export default { this.$emit('refetchContent'); }, selectBranch(newBranch) { - if (newBranch !== this.currentBranch) { - // If there are unsaved changes, we want to show the user - // a modal to confirm what to do with these before changing - // branches. - if (this.hasUnsavedChanges) { - this.branchSelected = newBranch; - this.$emit('select-branch', newBranch); - } else { - this.changeBranch(newBranch); - } + // If there are unsaved changes, we want to show the user + // a modal to confirm what to do with these before changing + // branches. + if (this.hasUnsavedChanges) { + this.branchSelected = newBranch; + this.$emit('select-branch', newBranch); + } else { + this.changeBranch(newBranch); } }, async setSearchTerm(newSearchTerm) { @@ -211,41 +208,23 @@ export default { </script> <template> - <gl-dropdown + <gl-collapsible-listbox + v-model="currentBranch" v-gl-tooltip.hover + data-qa-selector="branch_selector_button" + searchable + :items="branchesData" :title="$options.i18n.dropdownHeader" :header-text="$options.i18n.dropdownHeader" - :text="currentBranch" + :toggle-text="currentBranch" :disabled="!enableBranchSwitcher" icon="branch" data-testid="branch-selector" - > - <gl-search-box-by-type :debounce="$options.inputDebounce" @input="setSearchTerm" /> - <gl-dropdown-section-header> - {{ $options.i18n.title }} - </gl-dropdown-section-header> - - <gl-infinite-scroll - :fetched-items="availableBranches.length" - :max-list-height="250" - @bottomReached="fetchNextBranches" - > - <template #items> - <gl-dropdown-item - v-for="branch in availableBranches" - :key="branch" - :is-checked="currentBranch === branch" - is-check-item - @click="selectBranch(branch)" - > - {{ branch }} - </gl-dropdown-item> - </template> - <template #default> - <gl-dropdown-item v-if="isBranchesLoading" key="loading"> - <gl-loading-icon size="lg" /> - </gl-dropdown-item> - </template> - </gl-infinite-scroll> - </gl-dropdown> + :no-results-text="$options.i18n.fetchError" + :infinite-scroll-loading="areBranchesLoading" + :infinite-scroll="infiniteScrollEnabled" + @select="selectBranch" + @search="setSearchTerm" + @bottom-reached="fetchNextBranches" + /> </template> diff --git a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue index 44cf11acfe2..7c4a07e3f83 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue @@ -7,7 +7,7 @@ import getPipelineQuery from '~/ci/pipeline_editor/graphql/queries/pipeline.quer import getPipelineEtag from '~/ci/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql'; import { getQueryHeaders, toggleQueryPollingByVisibility } from '~/ci/pipeline_details/graph/utils'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import PipelineMiniGraph from '~/ci/pipeline_mini_graph/pipeline_mini_graph.vue'; import PipelineEditorMiniGraph from './pipeline_editor_mini_graph.vue'; @@ -25,7 +25,7 @@ export const i18n = { export default { i18n, components: { - CiBadgeLink, + CiIcon, GlButton, GlIcon, GlLink, @@ -155,14 +155,7 @@ export default { </template> <template v-else> <div class="gl-text-truncate gl-md-max-w-50p gl-mr-1"> - <a :href="status.detailsPath" class="gl-mr-auto"> - <ci-badge-link - :status="status" - size="md" - :show-text="false" - data-testid="pipeline-status-icon" - /> - </a> + <ci-icon :status="status" data-testid="pipeline-status-icon" /> <span class="gl-font-weight-bold"> <gl-sprintf :message="$options.i18n.pipelineInfo"> <template #id="{ content }"> diff --git a/app/assets/javascripts/ci/pipeline_editor/options.js b/app/assets/javascripts/ci/pipeline_editor/options.js index 922c8eee8fc..340cb6ab979 100644 --- a/app/assets/javascripts/ci/pipeline_editor/options.js +++ b/app/assets/javascripts/ci/pipeline_editor/options.js @@ -55,7 +55,6 @@ export const createAppOptions = (el) => { const apolloProvider = new VueApollo({ defaultClient: createDefaultClient(resolvers, { typeDefs, - useGet: true, }), }); const { cache } = apolloProvider.clients.defaultClient; diff --git a/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue index 41e5199e204..09ba6292e13 100644 --- a/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue +++ b/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_home.vue @@ -168,7 +168,7 @@ export default { @toggle-file-tree="toggleFileTree" v-on="$listeners" /> - <div class="gl-display-flex gl-w-full gl-sm-flex-direction-column"> + <div class="gl-display-flex gl-w-full gl-flex-direction-column gl-md-flex-direction-row"> <pipeline-editor-file-tree v-if="showFileTree" class="gl-flex-shrink-0" diff --git a/app/assets/javascripts/ci/pipeline_mini_graph/legacy_job_item.vue b/app/assets/javascripts/ci/pipeline_mini_graph/legacy_job_item.vue index d20d4aec59d..4fded3aec60 100644 --- a/app/assets/javascripts/ci/pipeline_mini_graph/legacy_job_item.vue +++ b/app/assets/javascripts/ci/pipeline_mini_graph/legacy_job_item.vue @@ -132,7 +132,6 @@ export default { <template> <div class="ci-job-component gl-display-flex gl-align-items-center gl-justify-content-space-between" - data-qa-selector="job_item_container" > <gl-link v-if="hasDetails" diff --git a/app/assets/javascripts/ci/pipeline_mini_graph/legacy_pipeline_stage.vue b/app/assets/javascripts/ci/pipeline_mini_graph/legacy_pipeline_stage.vue index 34640d49b80..ed78a335453 100644 --- a/app/assets/javascripts/ci/pipeline_mini_graph/legacy_pipeline_stage.vue +++ b/app/assets/javascripts/ci/pipeline_mini_graph/legacy_pipeline_stage.vue @@ -13,7 +13,7 @@ */ import { GlDropdown, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { createAlert } from '~/alert'; import eventHub from '~/ci/event_hub'; import axios from '~/lib/utils/axios_utils'; @@ -33,7 +33,7 @@ export default { positionFixed: true, }, components: { - CiBadgeLink, + CiIcon, GlLoadingIcon, GlDropdown, LegacyJobItem, @@ -126,14 +126,7 @@ export default { @show="onShowDropdown" > <template #button-content> - <ci-badge-link - :status="stage.status" - size="md" - :show-text="false" - :show-tooltip="false" - :use-link="false" - class="gl-mb-0!" - /> + <ci-icon :status="stage.status" :show-tooltip="false" :use-link="false" class="gl-mb-0!" /> </template> <div v-if="isLoading" class="gl--flex-center gl-p-2" data-testid="pipeline-stage-loading-state"> <gl-loading-icon size="sm" class="gl-mr-3" /> diff --git a/app/assets/javascripts/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue b/app/assets/javascripts/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue index cc703d29e23..f6a375ab94c 100644 --- a/app/assets/javascripts/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue +++ b/app/assets/javascripts/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue @@ -1,7 +1,7 @@ <script> import { GlTooltipDirective } from '@gitlab/ui'; import { sprintf, s__ } from '~/locale'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { accessValue } from './accessors/linked_pipelines_accessors'; /** * Renders the upstream/downstream portions of the pipeline mini graph. @@ -11,7 +11,7 @@ export default { GlTooltip: GlTooltipDirective, }, components: { - CiBadgeLink, + CiIcon, }, inject: { dataMethod: { @@ -81,11 +81,6 @@ export default { // detailedStatus is graphQL, details.status is REST return pipeline?.detailedStatus || pipeline?.details?.status; }, - triggerButtonClass(pipeline) { - const { group } = accessValue(pipeline, this.dataMethod, 'detailedStatus'); - - return `ci-status-icon-${group}`; - }, }, }; </script> @@ -99,15 +94,12 @@ export default { }" class="linked-pipeline-mini-list gl-display-inline gl-vertical-align-middle" > - <ci-badge-link + <ci-icon v-for="pipeline in linkedPipelinesTrimmed" :key="pipeline.id" v-gl-tooltip="{ title: pipelineTooltipText(pipeline) }" :status="pipelineStatus(pipeline)" - size="md" - :show-text="false" :show-tooltip="false" - :class="triggerButtonClass(pipeline)" class="linked-pipeline-mini-item gl-mb-0!" data-testid="linked-pipeline-mini-item" /> diff --git a/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue b/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue index 2f06b82bac0..722dc29d746 100644 --- a/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue +++ b/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue @@ -13,9 +13,9 @@ import { GlSprintf, GlLoadingIcon, } from '@gitlab/ui'; -import * as Sentry from '@sentry/browser'; import { uniqueId } from 'lodash'; import Vue from 'vue'; +import * as Sentry from '~/sentry/sentry_browser_wrapper'; import { fetchPolicies } from '~/lib/graphql'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue index cd1d9a97ef3..5444e66cbdf 100644 --- a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue +++ b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_form.vue @@ -371,11 +371,7 @@ export default { </gl-form-group> <!--Variable List--> <gl-form-group class="gl-mb-0" :label="$options.i18n.variables"> - <div - v-for="(variable, index) in variables" - :key="`var-${index}`" - data-qa-selector="ci_variable_row_container" - > + <div v-for="(variable, index) in variables" :key="`var-${index}`"> <div v-if="!variable.destroy" class="gl-display-flex gl-align-items-stretch gl-flex-direction-column gl-md-flex-direction-row gl-mb-3 gl-pb-2" diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions.vue b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions.vue index ed7c2bbeb73..78df7298f4f 100644 --- a/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions.vue +++ b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions.vue @@ -61,6 +61,7 @@ export default { v-if="canPlay" v-gl-tooltip :title="$options.i18n.playTooltip" + :aria-label="$options.i18n.playTooltip" icon="play" data-testid="play-pipeline-schedule-btn" @click="$emit('playPipelineSchedule', schedule.id)" @@ -78,6 +79,7 @@ export default { v-gl-tooltip :href="editPathWithIdParam" :title="$options.i18n.editTooltip" + :aria-label="$options.i18n.editTooltip" icon="pencil" data-testid="edit-pipeline-schedule-btn" /> @@ -85,6 +87,7 @@ export default { v-if="canRemove" v-gl-tooltip :title="$options.i18n.deleteTooltip" + :aria-label="$options.i18n.deleteTooltip" icon="remove" variant="danger" data-testid="delete-pipeline-schedule-btn" diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline.vue b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline.vue index 92f461c72d7..d979c0efaf2 100644 --- a/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline.vue +++ b/app/assets/javascripts/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline.vue @@ -1,9 +1,9 @@ <script> -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; export default { components: { - CiBadgeLink, + CiIcon, }, props: { schedule: { @@ -24,9 +24,10 @@ export default { <template> <div data-testid="last-pipeline-status"> - <ci-badge-link + <ci-icon v-if="hasPipeline" :status="lastPipelineStatus" + show-status-text class="gl-vertical-align-middle" /> <span v-else data-testid="pipeline-schedule-status-text"> diff --git a/app/assets/javascripts/ci/pipelines_page/components/empty_state/ios_templates.vue b/app/assets/javascripts/ci/pipelines_page/components/empty_state/ios_templates.vue deleted file mode 100644 index 1a2021df9c8..00000000000 --- a/app/assets/javascripts/ci/pipelines_page/components/empty_state/ios_templates.vue +++ /dev/null @@ -1,220 +0,0 @@ -<script> -import { GlButton, GlCard, GlSprintf, GlLink, GlPopover, GlModalDirective } from '@gitlab/ui'; -import { s__ } from '~/locale'; -import { helpPagePath } from '~/helpers/help_page_helper'; -import { mergeUrlParams, DOCS_URL } from '~/lib/utils/url_utility'; -import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; -import apolloProvider from '~/ci/pipeline_details/graphql/provider'; -import CiTemplates from './ci_templates.vue'; - -export default { - components: { - GlButton, - GlCard, - GlSprintf, - GlLink, - GlPopover, - RunnerInstructionsModal, - CiTemplates, - }, - directives: { - GlModalDirective, - }, - inject: ['pipelineEditorPath', 'iosRunnersAvailable'], - props: { - registrationToken: { - type: String, - required: false, - default: null, - }, - }, - apolloProvider, - iOSTemplateName: 'iOS-Fastlane', - modalId: 'runner-instructions-modal', - runnerDocsLink: `${DOCS_URL}/runner/install/osx`, - whatElseLink: helpPagePath('ci/index.md'), - i18n: { - title: s__('Pipelines|Get started with GitLab CI/CD'), - subtitle: s__('Pipelines|Building for iOS?'), - explanation: s__("Pipelines|We'll walk you through how to deploy to iOS in two easy steps."), - runnerSetupTitle: s__('Pipelines|1. Set up a runner'), - runnerSetupButton: s__('Pipelines|Set up a runner'), - runnerSetupBodyUnfinished: s__( - 'Pipelines|GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline.', - ), - runnerSetupBodyFinished: s__( - 'Pipelines|You have runners available to run your job now. No need to do anything else.', - ), - runnerSetupPopoverTitle: s__( - "Pipelines|Let's get that runner set up! %{emojiStart}tada%{emojiEnd}", - ), - runnerSetupPopoverBodyLine1: s__( - 'Pipelines|Follow these instructions to install GitLab Runner on macOS.', - ), - runnerSetupPopoverBodyLine2: s__( - 'Pipelines|Need more information to set up your runner? %{linkStart}Check out our documentation%{linkEnd}.', - ), - configurePipelineTitle: s__('Pipelines|2. Configure deployment pipeline'), - configurePipelineBody: s__("Pipelines|We'll guide you through a simple pipeline set-up."), - configurePipelineButton: s__('Pipelines|Configure pipeline'), - noWalkthroughTitle: s__("Pipelines|Don't need a guide? Jump in right away with a template."), - noWalkthroughExplanation: s__('Pipelines|Based on your project, we recommend this template:'), - notBuildingForIos: s__( - "Pipelines|Not building for iOS or not what you're looking for? %{linkStart}See what else%{linkEnd} GitLab CI/CD has to offer.", - ), - }, - data() { - return { - isModalShown: false, - isPopoverShown: false, - isRunnerSetupFinished: this.iosRunnersAvailable, - popoverTarget: `${this.$options.modalId}___BV_modal_content_`, - configurePipelineLink: mergeUrlParams( - { template: this.$options.iOSTemplateName }, - this.pipelineEditorPath, - ), - }; - }, - computed: { - runnerSetupBodyText() { - return this.iosRunnersAvailable - ? this.$options.i18n.runnerSetupBodyFinished - : this.$options.i18n.runnerSetupBodyUnfinished; - }, - }, - methods: { - showModal() { - this.isModalShown = true; - }, - hideModal() { - this.togglePopover(); - this.isRunnerSetupFinished = true; - }, - togglePopover() { - this.isPopoverShown = !this.isPopoverShown; - }, - }, -}; -</script> - -<template> - <div> - <h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.i18n.title }}</h2> - <h3 class="gl-font-lg gl-text-gray-900 gl-mt-1">{{ $options.i18n.subtitle }}</h3> - <p>{{ $options.i18n.explanation }}</p> - - <div class="gl-lg-display-flex"> - <div class="gl-lg-display-flex gl-lg-w-25p gl-lg-pr-4 gl-mb-4"> - <gl-card body-class="gl-display-flex gl-flex-grow-1"> - <div - class="gl-display-flex gl-flex-grow-1 gl-flex-direction-column gl-justify-content-space-between gl-align-items-flex-start" - > - <div> - <div class="gl-py-5"> - <gl-emoji - v-show="isRunnerSetupFinished" - class="gl-font-size-h2-xl" - data-name="white_check_mark" - data-testid="runner-setup-marked-completed" - /> - <gl-emoji - v-show="!isRunnerSetupFinished" - class="gl-font-size-h2-xl" - data-name="tools" - data-testid="runner-setup-marked-todo" - /> - </div> - <span class="gl-text-gray-800 gl-font-weight-bold"> - {{ $options.i18n.runnerSetupTitle }} - </span> - <p class="gl-font-sm gl-mt-3">{{ runnerSetupBodyText }}</p> - </div> - - <gl-button - v-if="!iosRunnersAvailable" - v-gl-modal-directive="$options.modalId" - category="primary" - variant="confirm" - @click="showModal" - > - {{ $options.i18n.runnerSetupButton }} - </gl-button> - <runner-instructions-modal - v-if="isModalShown" - :modal-id="$options.modalId" - :registration-token="registrationToken" - default-platform-name="osx" - @shown="togglePopover" - @hide="hideModal" - /> - <gl-popover - v-if="isPopoverShown" - :show="true" - :show-close-button="true" - :target="popoverTarget" - triggers="manual" - placement="left" - fallback-placement="clockwise" - > - <template #title> - <gl-sprintf :message="$options.i18n.runnerSetupPopoverTitle"> - <template #emoji="{ content }"> - <gl-emoji class="gl-ml-2" :data-name="content" /> - </template> - </gl-sprintf> - </template> - <div class="gl-mb-5"> - {{ $options.i18n.runnerSetupPopoverBodyLine1 }} - </div> - <gl-sprintf :message="$options.i18n.runnerSetupPopoverBodyLine2"> - <template #link="{ content }"> - <gl-link :href="$options.runnerDocsLink" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </gl-popover> - </div> - </gl-card> - </div> - <div class="gl-lg-display-flex gl-lg-w-25p gl-lg-pr-4 gl-mb-4"> - <gl-card body-class="gl-display-flex gl-flex-grow-1"> - <div - class="gl-display-flex gl-flex-grow-1 gl-flex-direction-column gl-justify-content-space-between gl-align-items-flex-start" - > - <div> - <div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="tools" /></div> - <span class="gl-text-gray-800 gl-font-weight-bold"> - {{ $options.i18n.configurePipelineTitle }} - </span> - <p class="gl-font-sm gl-mt-3">{{ $options.i18n.configurePipelineBody }}</p> - </div> - - <gl-button - :disabled="!isRunnerSetupFinished" - category="primary" - variant="confirm" - data-testid="configure-pipeline-link" - :href="configurePipelineLink" - > - {{ $options.i18n.configurePipelineButton }} - </gl-button> - </div> - </gl-card> - </div> - </div> - <h3 class="gl-font-lg gl-text-gray-900 gl-mt-5">{{ $options.i18n.noWalkthroughTitle }}</h3> - <p>{{ $options.i18n.noWalkthroughExplanation }}</p> - <ci-templates - :filter-templates="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [ - $options.iOSTemplateName, - ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" - :disabled="!isRunnerSetupFinished" - /> - <p> - <gl-sprintf :message="$options.i18n.notBuildingForIos"> - <template #link="{ content }"> - <gl-link :href="$options.whatElseLink">{{ content }}</gl-link> - </template> - </gl-sprintf> - </p> - </div> -</template> diff --git a/app/assets/javascripts/ci/pipelines_page/components/empty_state/no_ci_empty_state.vue b/app/assets/javascripts/ci/pipelines_page/components/empty_state/no_ci_empty_state.vue index 728e8541ae3..aed5f1d235d 100644 --- a/app/assets/javascripts/ci/pipelines_page/components/empty_state/no_ci_empty_state.vue +++ b/app/assets/javascripts/ci/pipelines_page/components/empty_state/no_ci_empty_state.vue @@ -1,9 +1,7 @@ <script> import { GlEmptyState } from '@gitlab/ui'; import { s__ } from '~/locale'; -import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue'; import PipelinesCiTemplates from './pipelines_ci_templates.vue'; -import IosTemplates from './ios_templates.vue'; export default { i18n: { @@ -12,9 +10,7 @@ export default { name: 'PipelinesEmptyState', components: { GlEmptyState, - GitlabExperiment, PipelinesCiTemplates, - IosTemplates, }, props: { emptyStateSvgPath: { @@ -25,30 +21,15 @@ export default { type: Boolean, required: true, }, - registrationToken: { - type: String, - required: false, - default: null, - }, }, }; </script> <template> - <div> - <gitlab-experiment v-if="canSetCi" name="ios_specific_templates"> - <template #control> - <pipelines-ci-templates /> - </template> - <template #candidate> - <ios-templates :registration-token="registrationToken" /> - </template> - </gitlab-experiment> - <gl-empty-state - v-else - title="" - :svg-path="emptyStateSvgPath" - :svg-height="null" - :description="$options.i18n.noCiDescription" - /> - </div> + <pipelines-ci-templates v-if="canSetCi" /> + <gl-empty-state + v-else + :svg-path="emptyStateSvgPath" + :svg-height="null" + :description="$options.i18n.noCiDescription" + /> </template> diff --git a/app/assets/javascripts/ci/pipelines_page/components/pipeline_labels.vue b/app/assets/javascripts/ci/pipelines_page/components/pipeline_labels.vue index 8f45094eb74..31d8f207a63 100644 --- a/app/assets/javascripts/ci/pipelines_page/components/pipeline_labels.vue +++ b/app/assets/javascripts/ci/pipelines_page/components/pipeline_labels.vue @@ -1,7 +1,7 @@ <script> import { GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; -import { SCHEDULE_ORIGIN } from '~/ci/pipeline_details/constants'; +import { SCHEDULE_ORIGIN, API_ORIGIN, TRIGGER_ORIGIN } from '../constants'; export default { components: { @@ -31,6 +31,9 @@ export default { isScheduled() { return this.pipeline.source === SCHEDULE_ORIGIN; }, + isTriggered() { + return this.pipeline.source === TRIGGER_ORIGIN; + }, isInFork() { return Boolean( this.targetProjectFullPath && @@ -50,6 +53,9 @@ export default { autoDevopsHelpPath() { return helpPagePath('topics/autodevops/index.md'); }, + isApi() { + return this.pipeline.source === API_ORIGIN; + }, }, }; </script> @@ -64,7 +70,16 @@ export default { variant="info" size="sm" data-testid="pipeline-url-scheduled" - >{{ __('Scheduled') }}</gl-badge + >{{ __('scheduled') }}</gl-badge + > + <gl-badge + v-if="isTriggered" + v-gl-tooltip + :title="__('This pipeline was created by an API call authenticated with a trigger token')" + variant="info" + size="sm" + data-testid="pipeline-url-triggered" + >{{ __('trigger token') }}</gl-badge > <gl-badge v-if="pipeline.flags.latest" @@ -185,5 +200,14 @@ export default { data-testid="pipeline-url-fork" >{{ __('fork') }}</gl-badge > + <gl-badge + v-if="isApi" + v-gl-tooltip + :title="__('This pipeline was triggered using the api')" + variant="info" + size="sm" + data-testid="pipeline-api-badge" + >{{ s__('Pipeline|api') }}</gl-badge + > </div> </template> diff --git a/app/assets/javascripts/ci/pipelines_page/components/pipeline_status_badge.vue b/app/assets/javascripts/ci/pipelines_page/components/pipeline_status_badge.vue index 20e2c7e9dce..380f8ce172f 100644 --- a/app/assets/javascripts/ci/pipelines_page/components/pipeline_status_badge.vue +++ b/app/assets/javascripts/ci/pipelines_page/components/pipeline_status_badge.vue @@ -1,12 +1,12 @@ <script> import { TRACKING_CATEGORIES } from '~/ci/constants'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import Tracking from '~/tracking'; import PipelinesTimeago from './time_ago.vue'; export default { components: { - CiBadgeLink, + CiIcon, PipelinesTimeago, }, mixins: [Tracking.mixin()], @@ -31,7 +31,12 @@ export default { <template> <div> - <ci-badge-link class="gl-mb-3" :status="pipelineStatus" @ciStatusBadgeClick="trackClick" /> + <ci-icon + class="gl-mb-2" + :status="pipelineStatus" + show-status-text + @ciStatusBadgeClick="trackClick" + /> <pipelines-timeago :pipeline="pipeline" /> </div> </template> diff --git a/app/assets/javascripts/ci/pipelines_page/components/pipeline_triggerer.vue b/app/assets/javascripts/ci/pipelines_page/components/pipeline_triggerer.vue index 2a73795db0a..a53c7cacae2 100644 --- a/app/assets/javascripts/ci/pipelines_page/components/pipeline_triggerer.vue +++ b/app/assets/javascripts/ci/pipelines_page/components/pipeline_triggerer.vue @@ -29,9 +29,5 @@ export default { <gl-avatar-link v-if="user" v-gl-tooltip :href="user.path" :title="user.name" class="gl-ml-3"> <gl-avatar :size="32" :src="user.avatar_url" /> </gl-avatar-link> - - <span v-else class="gl-ml-3"> - {{ s__('Pipelines|API') }} - </span> </div> </template> diff --git a/app/assets/javascripts/ci/pipelines_page/constants.js b/app/assets/javascripts/ci/pipelines_page/constants.js index aa6ef8a25ee..438eda44afe 100644 --- a/app/assets/javascripts/ci/pipelines_page/constants.js +++ b/app/assets/javascripts/ci/pipelines_page/constants.js @@ -1,2 +1,5 @@ export const ANY_TRIGGER_AUTHOR = 'Any'; export const FILTER_PIPELINES_SEARCH_DELAY = 200; +export const SCHEDULE_ORIGIN = 'schedule'; +export const API_ORIGIN = 'api'; +export const TRIGGER_ORIGIN = 'trigger'; diff --git a/app/assets/javascripts/ci/pipelines_page/pipelines.vue b/app/assets/javascripts/ci/pipelines_page/pipelines.vue index faa013079be..98e005a162f 100644 --- a/app/assets/javascripts/ci/pipelines_page/pipelines.vue +++ b/app/assets/javascripts/ci/pipelines_page/pipelines.vue @@ -4,7 +4,7 @@ import NO_PIPELINES_SVG from '@gitlab/svgs/dist/illustrations/empty-state/empty- import ERROR_STATE_SVG from '@gitlab/svgs/dist/illustrations/pipelines_failed.svg?url'; import { GlEmptyState, GlIcon, GlLoadingIcon, GlCollapsibleListbox } from '@gitlab/ui'; import { isEqual } from 'lodash'; -import * as Sentry from '@sentry/browser'; +import * as Sentry from '~/sentry/sentry_browser_wrapper'; import { createAlert, VARIANT_INFO, VARIANT_WARNING } from '~/alert'; import { getParameterByName } from '~/lib/utils/url_utility'; import { __, s__ } from '~/locale'; @@ -88,11 +88,6 @@ export default { type: Object, required: true, }, - registrationToken: { - type: String, - required: false, - default: null, - }, defaultVisibilityPipelineIdType: { type: String, required: false, @@ -311,6 +306,12 @@ export default { }, changeVisibilityPipelineIDType(idType) { this.visibilityPipelineIdType = idType; + if (idType === PIPELINE_IID_KEY) { + this.track('pipelines_display_options', { + label: TRACKING_CATEGORIES.listbox, + property: idType, + }); + } if (isLoggedIn()) { this.saveVisibilityPipelineIDType(idType); @@ -404,7 +405,6 @@ export default { v-else-if="stateToRender === $options.stateMap.emptyState" :empty-state-svg-path="$options.noPipelinesSvgPath" :can-set-ci="canCreatePipeline" - :registration-token="registrationToken" /> <gl-empty-state diff --git a/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue b/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue index f0a41a5949e..97163c1f55c 100644 --- a/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue +++ b/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue @@ -4,7 +4,6 @@ import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue'; -import RegistrationFeedbackBanner from '~/ci/runner/components/registration/registration_feedback_banner.vue'; import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue'; import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue'; import { DEFAULT_PLATFORM, PARAM_KEY_PLATFORM, INSTANCE_TYPE } from '../constants'; @@ -14,7 +13,6 @@ export default { name: 'AdminNewRunnerApp', components: { RegistrationCompatibilityAlert, - RegistrationFeedbackBanner, RunnerPlatformsRadioGroup, RunnerCreateForm, }, @@ -44,8 +42,6 @@ export default { <template> <div> - <registration-feedback-banner /> - <h1 class="gl-font-size-h2">{{ s__('Runners|New instance runner') }}</h1> <registration-compatibility-alert :alert-key="$options.INSTANCE_TYPE" /> diff --git a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue index 0ec94dc865f..1431f156c0e 100644 --- a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue +++ b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue @@ -14,6 +14,7 @@ import { import allRunnersQuery from 'ee_else_ce/ci/runner/graphql/list/all_runners.query.graphql'; import allRunnersCountQuery from 'ee_else_ce/ci/runner/graphql/list/all_runners_count.query.graphql'; +import RunnerListHeader from '../components/runner_list_header.vue'; import RegistrationDropdown from '../components/registration/registration_dropdown.vue'; import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; import RunnerList from '../components/runner_list.vue'; @@ -28,6 +29,7 @@ import RunnerJobStatusBadge from '../components/runner_job_status_badge.vue'; import { pausedTokenConfig } from '../components/search_tokens/paused_token_config'; import { statusTokenConfig } from '../components/search_tokens/status_token_config'; import { tagTokenConfig } from '../components/search_tokens/tag_token_config'; +import { versionTokenConfig } from '../components/search_tokens/version_token_config'; import { ADMIN_FILTERED_SEARCH_NAMESPACE, INSTANCE_TYPE, @@ -42,6 +44,7 @@ export default { components: { GlButton, GlLink, + RunnerListHeader, RegistrationDropdown, RunnerFilteredSearchBar, RunnerList, @@ -78,9 +81,6 @@ export default { apollo: { runners: { query: allRunnersQuery, - context: { - isSingleRequest: true, - }, fetchPolicy: fetchPolicies.NETWORK_ONLY, variables() { return this.variables; @@ -118,6 +118,7 @@ export default { return [ pausedTokenConfig, statusTokenConfig, + versionTokenConfig, { ...tagTokenConfig, recentSuggestionsStorageKey: `${this.$options.filteredSearchNamespace}-recent-tags`, @@ -178,11 +179,9 @@ export default { </script> <template> <div> - <header class="gl-my-5 gl-display-flex gl-justify-content-space-between"> - <h2 class="gl-my-0 header-title"> - {{ s__('Runners|Runners') }} - </h2> - <div class="gl-display-flex gl-gap-3"> + <runner-list-header> + <template #title>{{ s__('Runners|Runners') }}</template> + <template #actions> <runner-dashboard-link /> <gl-button :href="newRunnerPath" variant="confirm"> {{ s__('Runners|New instance runner') }} @@ -192,8 +191,9 @@ export default { :type="$options.INSTANCE_TYPE" placement="right" /> - </div> - </header> + </template> + </runner-list-header> + <div class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0" > diff --git a/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue b/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue index a80d6207be8..8a920c85e06 100644 --- a/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue +++ b/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue @@ -2,9 +2,9 @@ import { GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; import { sprintf, __, formatNumber } from '~/locale'; -import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; +import RunnerCreatedAt from '../runner_created_at.vue'; import RunnerName from '../runner_name.vue'; import RunnerTags from '../runner_tags.vue'; import RunnerTypeBadge from '../runner_type_badge.vue'; @@ -15,8 +15,6 @@ import { I18N_LOCKED_RUNNER_DESCRIPTION, I18N_VERSION_LABEL, I18N_LAST_CONTACT_LABEL, - I18N_CREATED_AT_LABEL, - I18N_CREATED_AT_BY_LABEL, } from '../../constants'; import RunnerSummaryField from './runner_summary_field.vue'; @@ -26,13 +24,13 @@ export default { GlSprintf, TimeAgo, RunnerSummaryField, + RunnerCreatedAt, RunnerName, RunnerTags, RunnerTypeBadge, RunnerManagersBadge, RunnerUpgradeStatusIcon: () => import('ee_component/ci/runner/components/runner_upgrade_status_icon.vue'), - UserAvatarLink, TooltipOnTruncate, }, directives: { @@ -75,8 +73,6 @@ export default { I18N_LOCKED_RUNNER_DESCRIPTION, I18N_VERSION_LABEL, I18N_LAST_CONTACT_LABEL, - I18N_CREATED_AT_LABEL, - I18N_CREATED_AT_BY_LABEL, }, }; </script> @@ -143,30 +139,7 @@ export default { </runner-summary-field> <runner-summary-field icon="calendar"> - <template v-if="createdBy"> - <gl-sprintf :message="$options.i18n.I18N_CREATED_AT_BY_LABEL"> - <template #timeAgo> - <time-ago v-if="runner.createdAt" :time="runner.createdAt" /> - </template> - <template #avatar> - <user-avatar-link - :link-href="createdBy.webUrl" - :img-src="createdBy.avatarUrl" - img-css-classes="gl-vertical-align-top" - :img-size="16" - :img-alt="createdByImgAlt" - :tooltip-text="createdBy.username" - /> - </template> - </gl-sprintf> - </template> - <template v-else> - <gl-sprintf :message="$options.i18n.I18N_CREATED_AT_LABEL"> - <template #timeAgo> - <time-ago v-if="runner.createdAt" :time="runner.createdAt" /> - </template> - </gl-sprintf> - </template> + <runner-created-at :runner="runner" /> </runner-summary-field> </div> diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue b/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue deleted file mode 100644 index 6fd4edf5847..00000000000 --- a/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue +++ /dev/null @@ -1,41 +0,0 @@ -<script> -import ILLUSTRATION_URL from '@gitlab/svgs/dist/illustrations/rocket-launch-md.svg?url'; -import { GlBanner } from '@gitlab/ui'; -import { s__ } from '~/locale'; -import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; - -const FEEDBACK_ISSUE_URL = 'https://gitlab.com/gitlab-org/gitlab/-/issues/387993'; - -export default { - components: { - GlBanner, - UserCalloutDismisser, - }, - i18n: { - title: s__("Runners|We've made some changes and want your feedback"), - body: s__( - "Runners|We've been making improvements to how you register runners so that it's more secure and efficient. Tell us how we're doing.", - ), - button: s__('Runners|Add your feedback to this issue'), - }, - ILLUSTRATION_URL, - FEEDBACK_ISSUE_URL, -}; -</script> -<template> - <user-callout-dismisser feature-name="create_runner_workflow_banner"> - <template #default="{ dismiss, shouldShowCallout }"> - <gl-banner - v-if="shouldShowCallout" - class="gl-my-6" - :title="$options.i18n.title" - :svg-path="$options.ILLUSTRATION_URL" - :button-text="$options.i18n.button" - :button-link="$options.FEEDBACK_ISSUE_URL" - @close="dismiss" - > - <p>{{ $options.i18n.body }}</p> - </gl-banner> - </template> - </user-callout-dismisser> -</template> diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue b/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue index 771ecb1a0d4..a4dec8199a3 100644 --- a/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue +++ b/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue @@ -100,11 +100,11 @@ export default { tokenMessage() { if (this.token) { return s__( - 'Runners|The %{boldStart}runner token%{boldEnd} %{token} displays %{boldStart}only for a short time%{boldEnd}, and is stored in the %{codeStart}config.toml%{codeEnd} after you register the runner. It will not be visible once the runner is registered.', + 'Runners|The %{boldStart}runner authentication token%{boldEnd} %{token} displays here %{boldStart}for a short time only%{boldEnd}. After you register the runner, this token is stored in the %{codeStart}config.toml%{codeEnd} and cannot be accessed again from the UI.', ); } return s__( - 'Runners|The %{boldStart}runner token%{boldEnd} is no longer visible, it is stored in the %{codeStart}config.toml%{codeEnd} if you have registered the runner.', + 'Runners|The %{boldStart}runner authentication token%{boldEnd} is no longer visible, it is stored in the %{codeStart}config.toml%{codeEnd} if you have registered the runner.', ); }, commandPrompt() { diff --git a/app/assets/javascripts/ci/runner/components/runner_created_at.vue b/app/assets/javascripts/ci/runner/components/runner_created_at.vue new file mode 100644 index 00000000000..410142a0eb5 --- /dev/null +++ b/app/assets/javascripts/ci/runner/components/runner_created_at.vue @@ -0,0 +1,72 @@ +<script> +import { GlSprintf, GlLink } from '@gitlab/ui'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; + +import { + I18N_CREATED_AT_LABEL, + I18N_CREATED_BY_LABEL, + I18N_CREATED_BY_AT_LABEL, +} from '../constants'; + +export default { + components: { + GlSprintf, + GlLink, + TimeAgo, + }, + props: { + runner: { + type: Object, + required: true, + }, + }, + computed: { + createdAt() { + return this.runner?.createdAt; + }, + createdBy() { + return this.runner?.createdBy; + }, + createdById() { + if (this.createdBy?.id) { + return getIdFromGraphQLId(this.createdBy.id); + } + return null; + }, + message() { + if (this.createdBy && this.createdAt) { + return I18N_CREATED_BY_AT_LABEL; + } + if (this.createdBy) { + return I18N_CREATED_BY_LABEL; + } + if (this.createdAt) { + return I18N_CREATED_AT_LABEL; + } + + return null; + }, + }, +}; +</script> +<template> + <span v-if="message"> + <gl-sprintf :message="message"> + <template #timeAgo> + <time-ago v-if="createdAt" :time="createdAt" /> + </template> + <template #user> + <gl-link + class="js-user-link gl-reset-color gl-font-weight-bold" + :href="createdBy.webUrl" + :data-user-id="createdById" + :data-username="createdBy.username" + :data-name="createdBy.name" + :data-avatar-url="createdBy.avatarUrl" + >{{ createdBy.name }}</gl-link + > + </template> + </gl-sprintf> + </span> +</template> diff --git a/app/assets/javascripts/ci/runner/components/runner_details.vue b/app/assets/javascripts/ci/runner/components/runner_details.vue index 0ec2ef30c20..477d28c6c28 100644 --- a/app/assets/javascripts/ci/runner/components/runner_details.vue +++ b/app/assets/javascripts/ci/runner/components/runner_details.vue @@ -120,12 +120,9 @@ export default { }} </p> <p class="gl-mb-0"> - <gl-link - :href="tokenExpirationHelpUrl" - target="_blank" - class="gl-reset-font-size" - >{{ __('Learn more') }}</gl-link - > + <gl-link :href="tokenExpirationHelpUrl" target="_blank">{{ + __('Learn more') + }}</gl-link> </p> </help-popover> </template> @@ -156,12 +153,9 @@ export default { " > <template #link="{ content }" - ><gl-link - :href="$options.RUNNER_MANAGERS_HELP_URL" - target="_blank" - class="gl-reset-font-size" - >{{ content }}</gl-link - ></template + ><gl-link :href="$options.RUNNER_MANAGERS_HELP_URL" target="_blank">{{ + content + }}</gl-link></template > </gl-sprintf> </help-popover> diff --git a/app/assets/javascripts/ci/runner/components/runner_header.vue b/app/assets/javascripts/ci/runner/components/runner_header.vue index 0fa06537ed6..f8d0352e532 100644 --- a/app/assets/javascripts/ci/runner/components/runner_header.vue +++ b/app/assets/javascripts/ci/runner/components/runner_header.vue @@ -1,16 +1,15 @@ <script> -import { GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; -import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; +import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { I18N_LOCKED_RUNNER_DESCRIPTION } from '../constants'; import { formatRunnerName } from '../utils'; +import RunnerCreatedAt from './runner_created_at.vue'; import RunnerTypeBadge from './runner_type_badge.vue'; import RunnerStatusBadge from './runner_status_badge.vue'; export default { components: { GlIcon, - GlSprintf, - TimeAgo, + RunnerCreatedAt, RunnerTypeBadge, RunnerStatusBadge, RunnerUpgradeStatusBadge: () => @@ -43,21 +42,13 @@ export default { <runner-status-badge :contacted-at="runner.contactedAt" :status="runner.status" /> <runner-type-badge :type="runner.runnerType" /> <runner-upgrade-status-badge :runner="runner" /> - <span v-if="runner.createdAt"> - <gl-sprintf :message="__('%{locked} created %{timeago}')"> - <template #locked> - <gl-icon - v-if="runner.locked" - v-gl-tooltip="$options.I18N_LOCKED_RUNNER_DESCRIPTION" - name="lock" - :aria-label="$options.I18N_LOCKED_RUNNER_DESCRIPTION" - /> - </template> - <template #timeago> - <time-ago :time="runner.createdAt" /> - </template> - </gl-sprintf> - </span> + <gl-icon + v-if="runner.locked" + v-gl-tooltip="$options.I18N_LOCKED_RUNNER_DESCRIPTION" + name="lock" + :aria-label="$options.I18N_LOCKED_RUNNER_DESCRIPTION" + /> + <runner-created-at :runner="runner" /> </div> </div> </template> diff --git a/app/assets/javascripts/ci/runner/components/runner_jobs_table.vue b/app/assets/javascripts/ci/runner/components/runner_jobs_table.vue index 5d8e9dcdee2..653d9b05330 100644 --- a/app/assets/javascripts/ci/runner/components/runner_jobs_table.vue +++ b/app/assets/javascripts/ci/runner/components/runner_jobs_table.vue @@ -3,7 +3,7 @@ import { GlTableLite } from '@gitlab/ui'; import { __, s__ } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { formatTime } from '~/lib/utils/datetime_utility'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import RunnerTags from '~/ci/runner/components/runner_tags.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import { tableField } from '../utils'; @@ -11,7 +11,7 @@ import LinkCell from './cells/link_cell.vue'; export default { components: { - CiBadgeLink, + CiIcon, GlTableLite, LinkCell, RunnerTags, @@ -80,7 +80,7 @@ export default { fixed > <template #cell(status)="{ item = {} }"> - <ci-badge-link v-if="item.detailedStatus" :status="item.detailedStatus" /> + <ci-icon v-if="item.detailedStatus" :status="item.detailedStatus" show-status-text /> </template> <template #cell(job)="{ item = {} }"> diff --git a/app/assets/javascripts/ci/runner/components/runner_list_header.vue b/app/assets/javascripts/ci/runner/components/runner_list_header.vue new file mode 100644 index 00000000000..e4367db035e --- /dev/null +++ b/app/assets/javascripts/ci/runner/components/runner_list_header.vue @@ -0,0 +1,17 @@ +<script> +export default { + name: 'RunnerListHeader', +}; +</script> +<template> + <header + class="gl-my-5 gl-display-flex gl-align-items-flex-start gl-flex-wrap gl-justify-content-space-between" + > + <h1 v-if="$scopedSlots.title" class="gl-my-0 gl-font-size-h1 header-title"> + <slot name="title"></slot> + </h1> + <div v-if="$scopedSlots.actions" class="gl-display-flex gl-gap-3"> + <slot name="actions"></slot> + </div> + </header> +</template> diff --git a/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue b/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue index dd1cca0a05c..1f61e878eb0 100644 --- a/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue +++ b/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue @@ -70,7 +70,7 @@ export default { @fetch-suggestions="fetchTags" v-on="$listeners" > - <template #view-token="{ viewTokenProps: { listeners, inputValue, activeTokenValue } }"> + <template #view-token="{ viewTokenProps: { listeners = {}, inputValue, activeTokenValue } }"> <gl-token variant="search-value" :class="$options.RUNNER_TAG_BG_CLASS" v-on="listeners"> {{ activeTokenValue ? activeTokenValue.text : inputValue }} </gl-token> diff --git a/app/assets/javascripts/ci/runner/components/search_tokens/version_token_config.js b/app/assets/javascripts/ci/runner/components/search_tokens/version_token_config.js new file mode 100644 index 00000000000..23f82d06f6d --- /dev/null +++ b/app/assets/javascripts/ci/runner/components/search_tokens/version_token_config.js @@ -0,0 +1,12 @@ +import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants'; +import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; +import { PARAM_KEY_VERSION, I18N_VERSION } from '../../constants'; + +export const versionTokenConfig = { + icon: 'doc-versions', + title: I18N_VERSION, + type: PARAM_KEY_VERSION, + token: BaseToken, + operators: OPERATORS_IS, + suggestionsDisabled: true, +}; diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js index b3cc295f8e4..d04d75b6e75 100644 --- a/app/assets/javascripts/ci/runner/constants.js +++ b/app/assets/javascripts/ci/runner/constants.js @@ -99,10 +99,14 @@ export const I18N_DELETED_TOAST = s__('Runners|Runner %{name} was deleted'); export const I18N_LOCKED_RUNNER_DESCRIPTION = s__( 'Runners|Runner is locked and available for currently assigned projects only. Only administrators can change the assigned projects.', ); +export const I18N_VERSION = s__('Runners|Version starts with'); export const I18N_VERSION_LABEL = s__('Runners|Version %{version}'); export const I18N_LAST_CONTACT_LABEL = s__('Runners|Last contact: %{timeAgo}'); + export const I18N_CREATED_AT_LABEL = s__('Runners|Created %{timeAgo}'); -export const I18N_CREATED_AT_BY_LABEL = s__('Runners|Created %{timeAgo} by %{avatar}'); +export const I18N_CREATED_BY_LABEL = s__('Runners|Created by %{user}'); +export const I18N_CREATED_BY_AT_LABEL = s__('Runners|Created by %{user} %{timeAgo}'); + export const I18N_SHOW_ONLY_INHERITED = s__('Runners|Show only inherited'); export const I18N_ADMIN = s__('Runners|Administrator'); @@ -154,6 +158,7 @@ export const PARAM_KEY_STATUS = 'status'; export const PARAM_KEY_PAUSED = 'paused'; export const PARAM_KEY_RUNNER_TYPE = 'runner_type'; export const PARAM_KEY_TAG = 'tag'; +export const PARAM_KEY_VERSION = 'version_prefix'; export const PARAM_KEY_SEARCH = 'search'; export const PARAM_KEY_MEMBERSHIP = 'membership'; diff --git a/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql index 41ec9967d90..5aa96f42b04 100644 --- a/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql +++ b/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql @@ -1,3 +1,5 @@ +#import "~/graphql_shared/fragments/user.fragment.graphql" + fragment RunnerFieldsShared on CiRunner { id shortSha @@ -10,5 +12,8 @@ fragment RunnerFieldsShared on CiRunner { maximumTimeout tagList createdAt + createdBy { + ...User + } status } diff --git a/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql b/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql index 15401c25c64..628ebfd2029 100644 --- a/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql +++ b/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql @@ -10,6 +10,7 @@ query getAllRunners( $type: CiRunnerType $tagList: [String!] $search: String + $versionPrefix: String $sort: CiRunnerSort ) { runners( @@ -22,6 +23,7 @@ query getAllRunners( type: $type tagList: $tagList search: $search + versionPrefix: $versionPrefix sort: $sort ) { ...AllRunnersConnection diff --git a/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql b/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql index 82591b88d3e..18f587495b0 100644 --- a/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql +++ b/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql @@ -4,8 +4,16 @@ query getAllRunnersCount( $type: CiRunnerType $tagList: [String!] $search: String + $versionPrefix: String ) { - runners(paused: $paused, status: $status, type: $type, tagList: $tagList, search: $search) { + runners( + paused: $paused + status: $status + type: $type + tagList: $tagList + search: $search + versionPrefix: $versionPrefix + ) { count } } diff --git a/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql index e2c890b3834..8f998ab42fa 100644 --- a/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql +++ b/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql @@ -1,3 +1,5 @@ +#import "~/graphql_shared/fragments/user.fragment.graphql" + fragment RunnerDetailsShared on CiRunner { id shortSha @@ -11,6 +13,9 @@ fragment RunnerDetailsShared on CiRunner { jobCount tagList createdAt + createdBy { + ...User + } status contactedAt tokenExpiresAt diff --git a/app/assets/javascripts/ci/runner/graphql/show/runner_jobs.query.graphql b/app/assets/javascripts/ci/runner/graphql/show/runner_jobs.query.graphql index b6d6996a857..611de43b995 100644 --- a/app/assets/javascripts/ci/runner/graphql/show/runner_jobs.query.graphql +++ b/app/assets/javascripts/ci/runner/graphql/show/runner_jobs.query.graphql @@ -8,7 +8,7 @@ query getRunnerJobs($id: CiRunnerID!, $first: Int, $last: Int, $before: String, nodes { id detailedStatus { - # fields for `<ci-badge-link>` + # fields for `<ci-icon>` id detailsPath group diff --git a/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue b/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue index 2e1706ddae9..c907f9c8982 100644 --- a/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue +++ b/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue @@ -4,7 +4,6 @@ import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue'; -import RegistrationFeedbackBanner from '~/ci/runner/components/registration/registration_feedback_banner.vue'; import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue'; import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue'; import { DEFAULT_PLATFORM, GROUP_TYPE, PARAM_KEY_PLATFORM } from '../constants'; @@ -14,7 +13,6 @@ export default { name: 'GroupNewRunnerApp', components: { RegistrationCompatibilityAlert, - RegistrationFeedbackBanner, RunnerPlatformsRadioGroup, RunnerCreateForm, }, @@ -50,8 +48,6 @@ export default { <template> <div> - <registration-feedback-banner /> - <h1 class="gl-font-size-h2">{{ s__('Runners|New group runner') }}</h1> <registration-compatibility-alert :alert-key="groupId" /> diff --git a/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue index dcaf8635f5c..b5042936b1e 100644 --- a/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue +++ b/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue @@ -14,6 +14,7 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import groupRunnersCountQuery from 'ee_else_ce/ci/runner/graphql/list/group_runners_count.query.graphql'; import groupRunnersQuery from 'ee_else_ce/ci/runner/graphql/list/group_runners.query.graphql'; +import RunnerListHeader from '../components/runner_list_header.vue'; import RegistrationDropdown from '../components/registration/registration_dropdown.vue'; import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; import RunnerList from '../components/runner_list.vue'; @@ -44,6 +45,7 @@ export default { components: { GlButton, GlLink, + RunnerListHeader, RegistrationDropdown, RunnerFilteredSearchBar, RunnerList, @@ -86,9 +88,6 @@ export default { apollo: { runners: { query: groupRunnersQuery, - context: { - isSingleRequest: true, - }, fetchPolicy: fetchPolicies.NETWORK_ONLY, variables() { return this.variables; @@ -212,11 +211,9 @@ export default { <template> <div> - <header class="gl-my-5 gl-display-flex gl-justify-content-space-between"> - <h2 class="gl-my-0 header-title"> - {{ s__('Runners|Runners') }} - </h2> - <div class="gl-display-flex gl-gap-3"> + <runner-list-header> + <template #title>{{ s__('Runners|Runners') }}</template> + <template #actions> <gl-button v-if="newRunnerPath" :href="newRunnerPath" @@ -231,8 +228,9 @@ export default { :type="$options.GROUP_TYPE" placement="right" /> - </div> - </header> + </template> + </runner-list-header> + <div class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0" > diff --git a/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue b/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue index 51f5a9ce8d9..241479a8c98 100644 --- a/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue +++ b/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue @@ -4,7 +4,6 @@ import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue'; -import RegistrationFeedbackBanner from '~/ci/runner/components/registration/registration_feedback_banner.vue'; import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue'; import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue'; import { DEFAULT_PLATFORM, PARAM_KEY_PLATFORM, PROJECT_TYPE } from '../constants'; @@ -14,7 +13,6 @@ export default { name: 'ProjectNewRunnerApp', components: { RegistrationCompatibilityAlert, - RegistrationFeedbackBanner, RunnerPlatformsRadioGroup, RunnerCreateForm, }, @@ -50,8 +48,6 @@ export default { <template> <div> - <registration-feedback-banner /> - <h1 class="gl-font-size-h2">{{ s__('Runners|New project runner') }}</h1> <registration-compatibility-alert :alert-key="projectId" /> diff --git a/app/assets/javascripts/ci/runner/runner_search_utils.js b/app/assets/javascripts/ci/runner/runner_search_utils.js index 8915198350f..e3aee15f42c 100644 --- a/app/assets/javascripts/ci/runner/runner_search_utils.js +++ b/app/assets/javascripts/ci/runner/runner_search_utils.js @@ -12,6 +12,7 @@ import { PARAM_KEY_STATUS, PARAM_KEY_RUNNER_TYPE, PARAM_KEY_TAG, + PARAM_KEY_VERSION, PARAM_KEY_SEARCH, PARAM_KEY_MEMBERSHIP, PARAM_KEY_SORT, @@ -151,7 +152,12 @@ export const fromUrlQueryToSearch = (query = window.location.search) => { membership: membership || DEFAULT_MEMBERSHIP, filters: prepareTokens( urlQueryToFilter(query, { - filterNamesAllowList: [PARAM_KEY_PAUSED, PARAM_KEY_STATUS, PARAM_KEY_TAG], + filterNamesAllowList: [ + PARAM_KEY_PAUSED, + PARAM_KEY_STATUS, + PARAM_KEY_TAG, + PARAM_KEY_VERSION, + ], filteredSearchTermKey: PARAM_KEY_SEARCH, }), ), @@ -178,6 +184,7 @@ export const fromSearchToUrl = ( [PARAM_KEY_MEMBERSHIP]: [], [PARAM_KEY_TAG]: [], [PARAM_KEY_PAUSED]: [], + [PARAM_KEY_VERSION]: [], // Current filters ...filterToQueryObject(processFilters(filters), { filteredSearchTermKey: PARAM_KEY_SEARCH, @@ -229,6 +236,7 @@ export const fromSearchToVariables = ({ [filterVariables.status] = queryObj[PARAM_KEY_STATUS] || []; filterVariables.search = queryObj[PARAM_KEY_SEARCH]; filterVariables.tagList = queryObj[PARAM_KEY_TAG]; + [filterVariables.versionPrefix] = queryObj[PARAM_KEY_VERSION] || []; if (queryObj[PARAM_KEY_PAUSED]) { filterVariables.paused = parseBoolean(queryObj[PARAM_KEY_PAUSED]); diff --git a/app/assets/javascripts/ci/runner/sentry_utils.js b/app/assets/javascripts/ci/runner/sentry_utils.js index 25fecdcfa7d..01a20880e0a 100644 --- a/app/assets/javascripts/ci/runner/sentry_utils.js +++ b/app/assets/javascripts/ci/runner/sentry_utils.js @@ -1,4 +1,4 @@ -import * as Sentry from '@sentry/browser'; +import * as Sentry from '~/sentry/sentry_browser_wrapper'; const COMPONENT_TAG = 'vue_component'; diff --git a/app/assets/javascripts/ci/utils.js b/app/assets/javascripts/ci/utils.js index 8a4f28404c6..21361aedb9d 100644 --- a/app/assets/javascripts/ci/utils.js +++ b/app/assets/javascripts/ci/utils.js @@ -1,4 +1,4 @@ -import * as Sentry from '@sentry/browser'; +import * as Sentry from '~/sentry/sentry_browser_wrapper'; export const reportToSentry = (component, failureType) => { Sentry.captureException(failureType, { |