diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-07 21:10:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-07 21:10:22 +0300 |
commit | 4d18bba787186aeb5bf8a0463fd145fae48b3234 (patch) | |
tree | daa26f7daa8bf303ef0cfcc1bfe68d13eae74537 /app/assets | |
parent | 33c86930e0a657e1519082a9a00faae260a44882 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
23 files changed, 465 insertions, 112 deletions
diff --git a/app/assets/javascripts/boards/components/board_app.vue b/app/assets/javascripts/boards/components/board_app.vue index 7d6bcfbe200..43b33acc045 100644 --- a/app/assets/javascripts/boards/components/board_app.vue +++ b/app/assets/javascripts/boards/components/board_app.vue @@ -6,8 +6,8 @@ import BoardContent from '~/boards/components/board_content.vue'; import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue'; import BoardTopBar from '~/boards/components/board_top_bar.vue'; import eventHub from '~/boards/eventhub'; -import { listsQuery } from 'ee_else_ce/boards/constants'; -import { formatBoardLists } from 'ee_else_ce/boards/boards_util'; +import { listsQuery, FilterFields } from 'ee_else_ce/boards/constants'; +import { formatBoardLists, filterVariables, FiltersInfo } from 'ee_else_ce/boards/boards_util'; import activeBoardItemQuery from 'ee_else_ce/boards/graphql/client/active_board_item.query.graphql'; import errorQuery from '../graphql/client/error.query.graphql'; import { setError } from '../graphql/cache_updates'; @@ -137,7 +137,12 @@ export default { setFilters(filters) { const filterParams = { ...filters }; if (filterParams.groupBy) delete filterParams.groupBy; - this.filterParams = filterParams; + this.filterParams = filterVariables({ + filters: filterParams, + issuableType: this.issuableType, + filterInfo: FiltersInfo, + filterFields: FilterFields, + }); }, }, }; diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 18495f285da..b514330fcdd 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -113,8 +113,8 @@ export default { this.$apollo.mutate({ mutation: setActiveBoardItemMutation, variables: { - boardItem: this.item, - isIssue: this.isIssueBoard, + boardItem: this.isActive ? null : this.item, + isIssue: this.isActive ? undefined : this.isIssueBoard, }, }); }, diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index f5e1e51cd0c..682d380cc20 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -8,6 +8,7 @@ import { defaultSortableOptions } from '~/sortable/constants'; import { sortableStart, sortableEnd } from '~/sortable/utils'; import Tracking from '~/tracking'; import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql'; +import setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql'; import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { @@ -49,6 +50,7 @@ export default { mixins: [Tracking.mixin(), glFeatureFlagMixin()], inject: [ 'isEpicBoard', + 'isIssueBoard', 'isGroupBoard', 'disabled', 'fullPath', @@ -578,6 +580,7 @@ export default { async addListItem(input) { this.toggleForm(); this.addItemToListInProgress = true; + let issuable; try { await this.$apollo.mutate({ mutation: listIssuablesQueries[this.issuableType].createMutation, @@ -586,7 +589,7 @@ export default { withColor: this.isEpicBoard && this.glFeatures.epicColorHighlight, }, update: (cache, { data: { createIssuable } }) => { - const { issuable } = createIssuable; + issuable = createIssuable.issuable; addItemToList({ query: listIssuablesQueries[this.issuableType].query, variables: { ...this.listQueryVariables, id: this.currentList.id }, @@ -626,6 +629,13 @@ export default { }); } finally { this.addItemToListInProgress = false; + this.$apollo.mutate({ + mutation: setActiveBoardItemMutation, + variables: { + boardItem: issuable, + isIssue: this.isIssueBoard, + }, + }); } }, }, diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index 2f87e3fd44f..ae12ca5feed 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -94,9 +94,12 @@ export default { parentType() { return this.boardType; }, - boardQuery() { + issueBoardsQuery() { return this.isGroupBoard ? groupBoardsQuery : projectBoardsQuery; }, + boardsQuery() { + return this.issueBoardsQuery; + }, loading() { return this.loadingRecentBoards || this.loadingBoards; }, @@ -161,7 +164,7 @@ export default { if (!data?.[this.parentType]) { return []; } - return data[this.parentType][boardType].edges.map(({ node }) => ({ + return data[this.parentType][boardType].nodes.map((node) => ({ id: getIdFromGraphQLId(node.id), name: node.name, })); @@ -178,7 +181,7 @@ export default { variables() { return { fullPath: this.fullPath }; }, - query: this.boardQuery, + query: this.boardsQuery, update: (data) => this.boardUpdate(data, 'boards'), watchLoading: (isLoading) => { this.loadingBoards = isLoading; @@ -217,19 +220,19 @@ export default { const { defaultClient: store } = this.$apollo.provider.clients; const sourceData = store.readQuery({ - query: this.boardQuery, + query: this.boardsQuery, variables: { fullPath: this.fullPath }, }); const newData = produce(sourceData, (draftState) => { - draftState[this.parentType].boards.edges = [ - ...draftState[this.parentType].boards.edges, - { node: board }, + draftState[this.parentType].boards.nodes = [ + ...draftState[this.parentType].boards.nodes, + { ...board }, ]; }); store.writeQuery({ - query: this.boardQuery, + query: this.boardsQuery, variables: { fullPath: this.fullPath }, data: newData, }); diff --git a/app/assets/javascripts/boards/components/config_toggle.vue b/app/assets/javascripts/boards/components/config_toggle.vue index dd3b9472879..b6ee3871b4f 100644 --- a/app/assets/javascripts/boards/components/config_toggle.vue +++ b/app/assets/javascripts/boards/components/config_toggle.vue @@ -29,7 +29,7 @@ export default { return this.canAdminList ? s__('Boards|Edit board') : s__('Boards|View scope'); }, tooltipTitle() { - return this.hasScope ? __("This board's scope is reduced") : ''; + return this.hasScope || this.boardHasScope ? __("This board's scope is reduced") : ''; }, }, methods: { diff --git a/app/assets/javascripts/boards/graphql/group_boards.query.graphql b/app/assets/javascripts/boards/graphql/group_boards.query.graphql index ce9f7bbfd2a..0fb5748abfc 100644 --- a/app/assets/javascripts/boards/graphql/group_boards.query.graphql +++ b/app/assets/javascripts/boards/graphql/group_boards.query.graphql @@ -2,11 +2,9 @@ query group_boards($fullPath: ID!) { group(fullPath: $fullPath) { id boards { - edges { - node { - id - name - } + nodes { + id + name } } } diff --git a/app/assets/javascripts/boards/graphql/group_recent_boards.query.graphql b/app/assets/javascripts/boards/graphql/group_recent_boards.query.graphql index b9fe778d4d4..9dbf4528cec 100644 --- a/app/assets/javascripts/boards/graphql/group_recent_boards.query.graphql +++ b/app/assets/javascripts/boards/graphql/group_recent_boards.query.graphql @@ -2,11 +2,9 @@ query group_recent_boards($fullPath: ID!) { group(fullPath: $fullPath) { id recentIssueBoards { - edges { - node { - id - name - } + nodes { + id + name } } } diff --git a/app/assets/javascripts/boards/graphql/project_boards.query.graphql b/app/assets/javascripts/boards/graphql/project_boards.query.graphql index 770c246a95b..97a298db246 100644 --- a/app/assets/javascripts/boards/graphql/project_boards.query.graphql +++ b/app/assets/javascripts/boards/graphql/project_boards.query.graphql @@ -2,11 +2,9 @@ query project_boards($fullPath: ID!) { project(fullPath: $fullPath) { id boards { - edges { - node { - id - name - } + nodes { + id + name } } } diff --git a/app/assets/javascripts/boards/graphql/project_recent_boards.query.graphql b/app/assets/javascripts/boards/graphql/project_recent_boards.query.graphql index c633107a409..0d3a8616603 100644 --- a/app/assets/javascripts/boards/graphql/project_recent_boards.query.graphql +++ b/app/assets/javascripts/boards/graphql/project_recent_boards.query.graphql @@ -2,11 +2,9 @@ query project_recent_boards($fullPath: ID!) { project(fullPath: $fullPath) { id recentIssueBoards { - edges { - node { - id - name - } + nodes { + id + name } } } diff --git a/app/assets/javascripts/environments/components/kubernetes_overview.vue b/app/assets/javascripts/environments/components/kubernetes_overview.vue index a1efeaac359..8c30520ebec 100644 --- a/app/assets/javascripts/environments/components/kubernetes_overview.vue +++ b/app/assets/javascripts/environments/components/kubernetes_overview.vue @@ -24,6 +24,10 @@ export default { required: true, type: Object, }, + environmentName: { + required: true, + type: String, + }, namespace: { required: false, type: String, @@ -96,7 +100,12 @@ export default { </p> <gl-collapse :visible="isVisible" class="gl-md-pl-7 gl-md-pr-5 gl-mt-4"> <template v-if="isVisible"> - <kubernetes-status-bar :cluster-health-status="clusterHealthStatus" class="gl-mb-4" /> + <kubernetes-status-bar + :cluster-health-status="clusterHealthStatus" + :configuration="k8sAccessConfiguration" + :namespace="namespace" + :environment-name="environmentName" + class="gl-mb-3" /> <kubernetes-agent-info :cluster-agent="clusterAgent" class="gl-mb-5" /> <gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mb-5"> diff --git a/app/assets/javascripts/environments/components/kubernetes_status_bar.vue b/app/assets/javascripts/environments/components/kubernetes_status_bar.vue index 94cd7438e46..b8859e65e38 100644 --- a/app/assets/javascripts/environments/components/kubernetes_status_bar.vue +++ b/app/assets/javascripts/environments/components/kubernetes_status_bar.vue @@ -1,7 +1,9 @@ <script> import { GlLoadingIcon, GlBadge } from '@gitlab/ui'; import { s__ } from '~/locale'; -import { HEALTH_BADGES } from '../constants'; +import { HEALTH_BADGES, SYNC_STATUS_BADGES, STATUS_TRUE, STATUS_FALSE } from '../constants'; +import fluxKustomizationStatusQuery from '../graphql/queries/flux_kustomization_status.query.graphql'; +import fluxHelmReleaseStatusQuery from '../graphql/queries/flux_helm_release_status.query.graphql'; export default { components: { @@ -17,23 +19,137 @@ export default { return ['error', 'success', ''].includes(val); }, }, + configuration: { + required: true, + type: Object, + }, + environmentName: { + required: true, + type: String, + }, + namespace: { + required: false, + type: String, + default: '', + }, + }, + apollo: { + fluxKustomizationStatus: { + query: fluxKustomizationStatusQuery, + variables() { + return { + configuration: this.configuration, + namespace: this.namespace, + environmentName: this.environmentName.toLowerCase(), + }; + }, + skip() { + return !this.namespace; + }, + }, + fluxHelmReleaseStatus: { + query: fluxHelmReleaseStatusQuery, + variables() { + return { + configuration: this.configuration, + namespace: this.namespace, + environmentName: this.environmentName.toLowerCase(), + }; + }, + skip() { + return Boolean( + !this.namespace || + this.$apollo.queries.fluxKustomizationStatus.loading || + this.hasKustomizations, + ); + }, + }, }, computed: { healthBadge() { return HEALTH_BADGES[this.clusterHealthStatus]; }, + hasKustomizations() { + return this.fluxKustomizationStatus?.length; + }, + hasHelmReleases() { + return this.fluxHelmReleaseStatus?.length; + }, + isLoading() { + return ( + this.$apollo.queries.fluxKustomizationStatus.loading || + this.$apollo.queries.fluxHelmReleaseStatus.loading + ); + }, + fluxCRD() { + if (!this.hasKustomizations && !this.hasHelmReleases) { + return []; + } + + return this.hasKustomizations ? this.fluxKustomizationStatus : this.fluxHelmReleaseStatus; + }, + fluxAnyStalled() { + return this.fluxCRD.find((condition) => { + return condition.status === STATUS_TRUE && condition.type === 'Stalled'; + }); + }, + fluxAnyReconciling() { + return this.fluxCRD.find((condition) => { + return condition.status === STATUS_TRUE && condition.type === 'Reconciling'; + }); + }, + fluxAnyReconciled() { + return this.fluxCRD.find((condition) => { + return condition.status === STATUS_TRUE && condition.type === 'Ready'; + }); + }, + fluxAnyFailed() { + return this.fluxCRD.find((condition) => { + return condition.status === STATUS_FALSE && condition.type === 'Ready'; + }); + }, + syncStatusBadge() { + if (!this.fluxCRD.length) { + return SYNC_STATUS_BADGES.unavailable; + } else if (this.fluxAnyFailed) { + return SYNC_STATUS_BADGES.failed; + } else if (this.fluxAnyStalled) { + return SYNC_STATUS_BADGES.stalled; + } else if (this.fluxAnyReconciling) { + return SYNC_STATUS_BADGES.reconciling; + } else if (this.fluxAnyReconciled) { + return SYNC_STATUS_BADGES.reconciled; + } + return SYNC_STATUS_BADGES.unknown; + }, }, i18n: { healthLabel: s__('Environment|Environment health'), + syncStatusLabel: s__('Environment|Sync status'), }, + badgeContainerClasses: 'gl-display-flex gl-align-items-center gl-flex-shrink-0 gl-mr-3 gl-mb-2', }; </script> <template> - <div class="gl-display-flex gl-align-items-center gl-mr-3 gl-mb-2"> - <span class="gl-font-sm gl-font-monospace gl-mr-3">{{ $options.i18n.healthLabel }}</span> - <gl-loading-icon v-if="!clusterHealthStatus" size="sm" inline /> - <gl-badge v-else-if="healthBadge" :variant="healthBadge.variant"> - {{ healthBadge.text }} - </gl-badge> + <div class="gl-display-flex gl-flex-wrap"> + <div :class="$options.badgeContainerClasses"> + <span class="gl-mr-3">{{ $options.i18n.healthLabel }}</span> + <gl-loading-icon v-if="!clusterHealthStatus" size="sm" inline /> + <gl-badge v-else-if="healthBadge" :variant="healthBadge.variant" data-testid="health-badge"> + {{ healthBadge.text }} + </gl-badge> + </div> + + <div :class="$options.badgeContainerClasses"> + <span class="gl-mr-3">{{ $options.i18n.syncStatusLabel }}</span> + <gl-loading-icon v-if="isLoading" size="sm" inline /> + <gl-badge + v-else-if="syncStatusBadge" + :icon="syncStatusBadge.icon" + :variant="syncStatusBadge.variant" + data-testid="sync-badge" + >{{ syncStatusBadge.text }}</gl-badge + > + </div> </div> </template> diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue index fda1c85f739..555d525c3b6 100644 --- a/app/assets/javascripts/environments/components/new_environment_item.vue +++ b/app/assets/javascripts/environments/components/new_environment_item.vue @@ -372,7 +372,11 @@ export default { </gl-sprintf> </div> <div v-if="clusterAgent" :class="$options.kubernetesOverviewClasses"> - <kubernetes-overview :cluster-agent="clusterAgent" :namespace="kubernetesNamespace" /> + <kubernetes-overview + :cluster-agent="clusterAgent" + :namespace="kubernetesNamespace" + :environment-name="environment.name" + /> </div> <div v-if="rolloutStatus" :class="$options.deployBoardClasses"> <deploy-board-wrapper diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js index dc9481a5429..921be902bdc 100644 --- a/app/assets/javascripts/environments/constants.js +++ b/app/assets/javascripts/environments/constants.js @@ -104,6 +104,42 @@ export const HEALTH_BADGES = { }, }; +export const SYNC_STATUS_BADGES = { + reconciled: { + variant: 'success', + icon: 'status_success', + text: s__('Environment|Reconciled'), + }, + reconciling: { + variant: 'info', + icon: 'status_running', + text: s__('Environment|Reconciling'), + }, + stalled: { + variant: 'warning', + icon: 'status_pending', + text: s__('Environment|Stalled'), + }, + failed: { + variant: 'danger', + icon: 'status_failed', + text: s__('Deployment|Failed'), + }, + unknown: { + variant: 'neutral', + icon: 'status_notfound', + text: s__('Deployment|Unknown'), + }, + unavailable: { + variant: 'muted', + icon: 'status_notfound', + text: s__('Deployment|Unavailable'), + }, +}; + +export const STATUS_TRUE = 'True'; +export const STATUS_FALSE = 'False'; + export const PHASE_RUNNING = 'Running'; export const PHASE_PENDING = 'Pending'; export const PHASE_SUCCEEDED = 'Succeeded'; diff --git a/app/assets/javascripts/environments/graphql/client.js b/app/assets/javascripts/environments/graphql/client.js index 553b06e632f..8faed710402 100644 --- a/app/assets/javascripts/environments/graphql/client.js +++ b/app/assets/javascripts/environments/graphql/client.js @@ -9,6 +9,8 @@ import k8sPodsQuery from './queries/k8s_pods.query.graphql'; import k8sServicesQuery from './queries/k8s_services.query.graphql'; import k8sWorkloadsQuery from './queries/k8s_workloads.query.graphql'; import k8sNamespacesQuery from './queries/k8s_namespaces.query.graphql'; +import fluxKustomizationStatusQuery from './queries/flux_kustomization_status.query.graphql'; +import fluxHelmReleaseStatusQuery from './queries/flux_helm_release_status.query.graphql'; import { resolvers } from './resolvers'; import typeDefs from './typedefs.graphql'; @@ -170,6 +172,21 @@ export const apolloProvider = (endpoint) => { }, }, }); + cache.writeQuery({ + query: fluxKustomizationStatusQuery, + data: { + status: '', + type: '', + }, + }); + cache.writeQuery({ + query: fluxHelmReleaseStatusQuery, + data: { + status: '', + type: '', + }, + }); + return new VueApollo({ defaultClient, }); diff --git a/app/assets/javascripts/environments/graphql/queries/flux_helm_release_status.query.graphql b/app/assets/javascripts/environments/graphql/queries/flux_helm_release_status.query.graphql new file mode 100644 index 00000000000..0857f6f57df --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/flux_helm_release_status.query.graphql @@ -0,0 +1,14 @@ +query getFluxHelmReleaseStatusQuery( + $configuration: LocalConfiguration + $namespace: String + $environmentName: String +) { + fluxHelmReleaseStatus( + configuration: $configuration + namespace: $namespace + environmentName: $environmentName + ) @client { + status + type + } +} diff --git a/app/assets/javascripts/environments/graphql/queries/flux_kustomization_status.query.graphql b/app/assets/javascripts/environments/graphql/queries/flux_kustomization_status.query.graphql new file mode 100644 index 00000000000..4eaea014718 --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/flux_kustomization_status.query.graphql @@ -0,0 +1,14 @@ +query getFluxHelmKustomizationStatusQuery( + $configuration: LocalConfiguration + $namespace: String + $environmentName: String +) { + fluxKustomizationStatus( + configuration: $configuration + namespace: $namespace + environmentName: $environmentName + ) @client { + status + type + } +} diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js index 8cfe44c5a05..fcc17cea4e1 100644 --- a/app/assets/javascripts/environments/graphql/resolvers.js +++ b/app/assets/javascripts/environments/graphql/resolvers.js @@ -16,6 +16,11 @@ import environmentToChangeCanaryQuery from './queries/environment_to_change_cana import isEnvironmentStoppingQuery from './queries/is_environment_stopping.query.graphql'; import pageInfoQuery from './queries/page_info.query.graphql'; +const helmReleasesResourceType = 'helmreleases'; +const kustomizationsResourceType = 'kustomizations'; +const helmReleasesApiVersion = 'helm.toolkit.fluxcd.io/v2beta1'; +const kustomizationsApiVersion = 'kustomize.toolkit.fluxcd.io/v1beta1'; + const buildErrors = (errors = []) => ({ errors, __typename: 'LocalEnvironmentErrors', @@ -78,6 +83,30 @@ const handleClusterError = (err) => { throw error; }; +const buildFluxResourceUrl = ({ + basePath, + namespace, + apiVersion, + resourceType, + environmentName, +}) => { + return `${basePath}/apis/${apiVersion}/namespaces/${namespace}/${resourceType}/${environmentName}`; +}; + +const getFluxResourceStatus = (configuration, url) => { + const { headers } = configuration.baseOptions; + const withCredentials = true; + + return axios + .get(url, { withCredentials, headers }) + .then((res) => { + return res?.data?.status?.conditions || []; + }) + .catch((err) => { + handleClusterError(err); + }); +}; + export const resolvers = (endpoint) => ({ Query: { environmentApp(_context, { page, scope, search }, { cache }) { @@ -223,6 +252,26 @@ export const resolvers = (endpoint) => ({ throw new Error(humanizeClusterErrors(error)); }); }, + fluxKustomizationStatus(_, { configuration, namespace, environmentName }) { + const url = buildFluxResourceUrl({ + basePath: configuration.basePath, + resourceType: kustomizationsResourceType, + apiVersion: kustomizationsApiVersion, + namespace, + environmentName, + }); + return getFluxResourceStatus(configuration, url); + }, + fluxHelmReleaseStatus(_, { configuration, namespace, environmentName }) { + const url = buildFluxResourceUrl({ + basePath: configuration.basePath, + resourceType: helmReleasesResourceType, + apiVersion: helmReleasesApiVersion, + namespace, + environmentName, + }); + return getFluxResourceStatus(configuration, url); + }, }, Mutation: { stopEnvironmentREST(_, { environment }, { client }) { diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql index e2c22dda554..41f165ad1da 100644 --- a/app/assets/javascripts/environments/graphql/typedefs.graphql +++ b/app/assets/javascripts/environments/graphql/typedefs.graphql @@ -167,6 +167,11 @@ type LocalK8sNamespaces { metadata: k8sNamespaceMetadata } +type LocalFluxResourceStatus { + status: String + type: String +} + extend type Query { environmentApp(page: Int, scope: String): LocalEnvironmentApp folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder @@ -179,6 +184,16 @@ extend type Query { k8sPods(configuration: LocalConfiguration, namespace: String): [LocalK8sPods] k8sServices(configuration: LocalConfiguration): [LocalK8sServices] k8sWorkloads(configuration: LocalConfiguration, namespace: String): LocalK8sWorkloads + fluxKustomizationStatus( + configuration: LocalConfiguration + namespace: String + environmentName: String + ): LocalFluxResourceStatus + fluxHelmReleaseStatus( + configuration: LocalConfiguration + namespace: String + environmentName: String + ): LocalFluxResourceStatus } extend type Mutation { diff --git a/app/assets/javascripts/group_settings/components/shared_runners_form.vue b/app/assets/javascripts/group_settings/components/shared_runners_form.vue index a4ec48ffd2f..d396169c0a3 100644 --- a/app/assets/javascripts/group_settings/components/shared_runners_form.vue +++ b/app/assets/javascripts/group_settings/components/shared_runners_form.vue @@ -1,5 +1,5 @@ <script> -import { GlToggle, GlAlert } from '@gitlab/ui'; +import { GlAlert, GlLink, GlSprintf, GlToggle } from '@gitlab/ui'; import { sprintf } from '~/locale'; import { updateGroup } from '~/api/groups_api'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; @@ -14,19 +14,29 @@ import { export default { components: { - GlToggle, GlAlert, + GlLink, + GlSprintf, + GlToggle, + }, + inject: { + groupId: {}, + groupName: {}, + groupIsEmpty: {}, + sharedRunnersSetting: {}, + + runnerEnabledValue: {}, + runnerDisabledValue: {}, + runnerAllowOverrideValue: {}, + + // Parent group, only present in sub-groups + + parentSharedRunnersSetting: { default: null }, + + // Available when user can admin parent + parentName: { default: null }, + parentSettingsPath: { default: null }, }, - inject: [ - 'groupId', - 'groupName', - 'groupIsEmpty', - 'sharedRunnersSetting', - 'parentSharedRunnersSetting', - 'runnerEnabledValue', - 'runnerDisabledValue', - 'runnerAllowOverrideValue', - ], data() { return { isLoading: false, @@ -48,6 +58,9 @@ export default { overrideToggleValue() { return this.value === this.runnerAllowOverrideValue; }, + isParentAvailable() { + return this.parentSettingsPath && this.parentName; + }, }, methods: { async onSharedRunnersToggle(enabled) { @@ -109,26 +122,28 @@ export default { <gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mb-5"> {{ error }} </gl-alert> - - <gl-alert - v-if="isSharedRunnersToggleDisabled" - variant="warning" - :dismissible="false" - class="gl-mb-5" - > - {{ __('Shared runners are disabled for the parent group') }} - </gl-alert> - <section class="gl-mb-5"> <gl-toggle :value="sharedRunnersToggleValue" :is-loading="isLoading" :disabled="isSharedRunnersToggleDisabled" :label="__('Enable shared runners for this group')" - :help="__('Enable shared runners for all projects and subgroups in this group.')" + :description="__('Enable shared runners for all projects and subgroups in this group.')" data-testid="shared-runners-toggle" @change="onSharedRunnersToggle" - /> + > + <template v-if="isSharedRunnersToggleDisabled" #help> + {{ s__('Runners|Shared runners are disabled.') }} + <gl-sprintf + v-if="isParentAvailable" + :message="s__('Runners|Go to %{groupLink} to enable them.')" + > + <template #groupLink> + <gl-link :href="parentSettingsPath">{{ parentName }}</gl-link> + </template> + </gl-sprintf> + </template> + </gl-toggle> </section> <section class="gl-mb-5"> @@ -137,10 +152,24 @@ export default { :is-loading="isLoading" :disabled="isOverrideToggleDisabled" :label="__('Allow projects and subgroups to override the group setting')" - :help="__('Allows projects or subgroups in this group to override the global setting.')" + :description=" + __('Allows projects or subgroups in this group to override the global setting.') + " data-testid="override-runners-toggle" @change="onOverrideToggle" - /> + > + <template v-if="isSharedRunnersToggleDisabled" #help> + {{ s__('Runners|Shared runners are disabled.') }} + <gl-sprintf + v-if="isParentAvailable" + :message="s__('Runners|Go to %{groupLink} to enable them.')" + > + <template #groupLink> + <gl-link :href="parentSettingsPath">{{ parentName }}</gl-link> + </template> + </gl-sprintf> + </template> + </gl-toggle> </section> </div> </template> diff --git a/app/assets/javascripts/group_settings/mount_shared_runners.js b/app/assets/javascripts/group_settings/mount_shared_runners.js index 0767330cd54..334192a6f87 100644 --- a/app/assets/javascripts/group_settings/mount_shared_runners.js +++ b/app/assets/javascripts/group_settings/mount_shared_runners.js @@ -10,6 +10,8 @@ export default (containerId = 'update-shared-runners-form') => { groupName, groupIsEmpty, sharedRunnersSetting, + parentName, + parentSettingsPath, parentSharedRunnersSetting, runnerEnabledValue, runnerDisabledValue, @@ -23,10 +25,14 @@ export default (containerId = 'update-shared-runners-form') => { groupName, groupIsEmpty: parseBoolean(groupIsEmpty), sharedRunnersSetting, - parentSharedRunnersSetting, + runnerEnabledValue, runnerDisabledValue, runnerAllowOverrideValue, + + parentName, + parentSettingsPath, + parentSharedRunnersSetting, }, render(createElement) { return createElement(UpdateSharedRunnersForm); diff --git a/app/assets/javascripts/token_access/components/inbound_token_access.vue b/app/assets/javascripts/token_access/components/inbound_token_access.vue index ac359b4f901..234ac0505b2 100644 --- a/app/assets/javascripts/token_access/components/inbound_token_access.vue +++ b/app/assets/javascripts/token_access/components/inbound_token_access.vue @@ -5,6 +5,7 @@ import { GlCard, GlFormInput, GlLink, + GlIcon, GlLoadingIcon, GlSprintf, GlToggle, @@ -64,6 +65,7 @@ export default { GlCard, GlFormInput, GlLink, + GlIcon, GlLoadingIcon, GlSprintf, GlToggle, @@ -109,6 +111,7 @@ export default { inboundJobTokenScopeEnabled: null, targetProjectPath: '', projects: [], + isAddFormVisible: false, }; }, computed: { @@ -193,10 +196,14 @@ export default { }, clearTargetProjectPath() { this.targetProjectPath = ''; + this.isAddFormVisible = false; }, getProjects() { this.$apollo.queries.projects.refetch(); }, + showAddForm() { + this.isAddFormVisible = true; + }, }, }; </script> @@ -228,22 +235,55 @@ export default { </gl-toggle> <div> - <gl-card class="gl-mt-5 gl-mb-3"> + <gl-card + class="gl-new-card" + header-class="gl-new-card-header" + body-class="gl-new-card-body gl-px-0" + > <template #header> - <h5 class="gl-my-0">{{ $options.i18n.cardHeaderTitle }}</h5> + <div class="gl-new-card-title-wrapper"> + <h5 class="gl-new-card-title">{{ $options.i18n.cardHeaderTitle }}</h5> + <span class="gl-new-card-count"> + <gl-icon name="project" class="gl-mr-2" /> + {{ projects.length }} + </span> + </div> + <div class="gl-new-card-actions"> + <gl-button + v-if="!isAddFormVisible" + size="small" + data-testid="toggle-form-btn" + @click="showAddForm" + >{{ $options.i18n.addProject }}</gl-button + > + </div> </template> - <template #default> + + <div v-if="isAddFormVisible" class="gl-new-card-add-form gl-m-3"> + <h4 class="gl-mt-0">{{ $options.i18n.addProject }}</h4> <gl-form-input v-model="targetProjectPath" :placeholder="$options.i18n.addProjectPlaceholder" /> - </template> - <template #footer> - <gl-button variant="confirm" :disabled="isProjectPathEmpty" @click="addProject"> - {{ $options.i18n.addProject }} - </gl-button> - <gl-button @click="clearTargetProjectPath">{{ $options.i18n.cancel }}</gl-button> - </template> + <div class="gl-display-flex gl-mt-5"> + <gl-button + variant="confirm" + :disabled="isProjectPathEmpty" + class="gl-mr-3" + data-testid="add-project-btn" + @click="addProject" + > + {{ $options.i18n.addProject }} + </gl-button> + <gl-button @click="clearTargetProjectPath">{{ $options.i18n.cancel }}</gl-button> + </div> + </div> + + <token-projects-table + :projects="projects" + :table-fields="$options.fields" + @removeProject="removeProject" + /> </gl-card> <gl-alert v-if="!inboundJobTokenScopeEnabled" @@ -254,11 +294,6 @@ export default { > {{ $options.i18n.settingDisabledMessage }} </gl-alert> - <token-projects-table - :projects="projects" - :table-fields="$options.fields" - @removeProject="removeProject" - /> </div> </template> </div> diff --git a/app/assets/javascripts/token_access/components/outbound_token_access.vue b/app/assets/javascripts/token_access/components/outbound_token_access.vue index cad1fff062a..7e1e6cc445c 100644 --- a/app/assets/javascripts/token_access/components/outbound_token_access.vue +++ b/app/assets/javascripts/token_access/components/outbound_token_access.vue @@ -3,8 +3,8 @@ import { GlAlert, GlButton, GlCard, - GlFormInput, GlLink, + GlIcon, GlLoadingIcon, GlSprintf, GlToggle, @@ -71,8 +71,8 @@ export default { GlAlert, GlButton, GlCard, - GlFormInput, GlLink, + GlIcon, GlLoadingIcon, GlSprintf, GlToggle, @@ -221,7 +221,7 @@ export default { <gl-loading-icon v-if="$apollo.loading" size="lg" class="gl-mt-5" /> <template v-else> <gl-alert - class="gl-mb-3" + class="gl-mt-5 gl-mb-3" variant="warning" :dismissible="false" :show-icon="false" @@ -268,30 +268,29 @@ export default { </gl-toggle> <div> - <gl-card class="gl-mt-5 gl-mb-3"> + <gl-card + class="gl-new-card" + header-class="gl-new-card-header" + body-class="gl-new-card-body gl-px-0" + > <template #header> - <h5 class="gl-my-0">{{ $options.i18n.cardHeaderTitle }}</h5> - </template> - <template #default> - <gl-form-input - v-model="targetProjectPath" - :disabled="true" - :placeholder="$options.i18n.addProjectPlaceholder" - data-testid="project-path-input" - /> - </template> - <template #footer> - <gl-button variant="confirm" :disabled="isProjectPathEmpty" @click="addProject"> - {{ $options.i18n.addProject }} - </gl-button> - <gl-button @click="clearTargetProjectPath">{{ $options.i18n.cancel }}</gl-button> + <div class="gl-new-card-title-wrapper"> + <h5 class="gl-new-card-title">{{ $options.i18n.cardHeaderTitle }}</h5> + <span class="gl-new-card-count"> + <gl-icon name="project" class="gl-mr-2" /> + {{ projects.length }} + </span> + </div> + <div class="gl-new-card-actions"> + <gl-button size="small" disabled>{{ $options.i18n.addProject }}</gl-button> + </div> </template> + <token-projects-table + :projects="projects" + :table-fields="$options.fields" + @removeProject="removeProject" + /> </gl-card> - <token-projects-table - :projects="projects" - :table-fields="$options.fields" - @removeProject="removeProject" - /> </div> </template> </div> diff --git a/app/assets/javascripts/token_access/components/token_access_app.vue b/app/assets/javascripts/token_access/components/token_access_app.vue index 167eebc8d9b..d2d5e6b2a5a 100644 --- a/app/assets/javascripts/token_access/components/token_access_app.vue +++ b/app/assets/javascripts/token_access/components/token_access_app.vue @@ -12,7 +12,7 @@ export default { </script> <template> <div> - <inbound-token-access class="gl-pb-5" /> - <outbound-token-access class="gl-py-5" /> + <inbound-token-access /> + <outbound-token-access /> </div> </template> |