diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 13:49:51 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 13:49:51 +0000 |
commit | 71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e (patch) | |
tree | 6a2d93ef3fb2d353bb7739e4b57e6541f51cdd71 /app/assets/javascripts/environments | |
parent | a7253423e3403b8c08f8a161e5937e1488f5f407 (diff) | |
download | gitlab-ce-71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e.tar.gz |
Add latest changes from gitlab-org/gitlab@15-9-stable-eev15.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/environments')
14 files changed, 263 insertions, 11 deletions
diff --git a/app/assets/javascripts/environments/components/environment_form.vue b/app/assets/javascripts/environments/components/environment_form.vue index 1bac0ef1359..ee5d95ae6f0 100644 --- a/app/assets/javascripts/environments/components/environment_form.vue +++ b/app/assets/javascripts/environments/components/environment_form.vue @@ -3,6 +3,10 @@ import { GlButton, GlForm, GlFormGroup, GlFormInput, GlLink, GlSprintf } from '@ import { helpPagePath } from '~/helpers/help_page_helper'; import { isAbsolute } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; +import { + ENVIRONMENT_NEW_HELP_TEXT, + ENVIRONMENT_EDIT_HELP_TEXT, +} from 'ee_else_ce/environments/constants'; export default { components: { @@ -13,6 +17,7 @@ export default { GlLink, GlSprintf, }, + inject: ['protectedEnvironmentSettingsPath'], props: { environment: { required: true, @@ -34,9 +39,8 @@ export default { }, i18n: { header: __('Environments'), - helpMessage: __( - 'Environments allow you to track deployments of your application. %{linkStart}More information%{linkEnd}.', - ), + helpNewMessage: ENVIRONMENT_NEW_HELP_TEXT, + helpEditMessage: ENVIRONMENT_EDIT_HELP_TEXT, nameLabel: __('Name'), nameFeedback: __('This field is required'), nameDisabledHelp: __("You cannot rename an environment after it's created."), @@ -62,6 +66,9 @@ export default { isNameDisabled() { return Boolean(this.environment.id); }, + showEditHelp() { + return this.isNameDisabled && Boolean(this.protectedEnvironmentSettingsPath); + }, valid() { return { name: this.visited.name && this.environment.name !== '', @@ -89,9 +96,14 @@ export default { {{ $options.i18n.header }} </h4> <p class="gl-w-full"> - <gl-sprintf :message="$options.i18n.helpMessage"> + <gl-sprintf + :message="showEditHelp ? $options.i18n.helpEditMessage : $options.i18n.helpNewMessage" + > <template #link="{ content }"> - <gl-link :href="$options.helpPagePath">{{ content }}</gl-link> + <gl-link + :href="showEditHelp ? protectedEnvironmentSettingsPath : $options.helpPagePath" + >{{ content }}</gl-link + > </template> </gl-sprintf> </p> diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index 55e6a891e27..b2a69cdb6c6 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -15,6 +15,7 @@ import { ENVIRONMENTS_SCOPE } from '../constants'; import EnvironmentFolder from './environment_folder.vue'; import EnableReviewAppModal from './enable_review_app_modal.vue'; import StopEnvironmentModal from './stop_environment_modal.vue'; +import StopStaleEnvironmentsModal from './stop_stale_environments_modal.vue'; import EnvironmentItem from './new_environment_item.vue'; import ConfirmRollbackModal from './confirm_rollback_modal.vue'; import DeleteEnvironmentModal from './delete_environment_modal.vue'; @@ -31,6 +32,7 @@ export default { EnableReviewAppModal, EnvironmentItem, StopEnvironmentModal, + StopStaleEnvironmentsModal, GlBadge, GlPagination, GlSearchBoxByType, @@ -75,6 +77,7 @@ export default { i18n: { newEnvironmentButtonLabel: s__('Environments|New environment'), reviewAppButtonLabel: s__('Environments|Enable review app'), + cleanUpEnvsButtonLabel: s__('Environments|Clean up environments'), available: __('Available'), stopped: __('Stopped'), prevPage: __('Go to previous page'), @@ -85,11 +88,13 @@ export default { searchPlaceholder: s__('Environments|Search by environment name'), }, modalId: 'enable-review-app-info', + stopStaleEnvsModalId: 'stop-stale-environments-modal', data() { const { page = '1', search = '', scope } = queryToObject(window.location.search); return { interval: undefined, isReviewAppModalVisible: false, + isStopStaleEnvModalVisible: false, page: parseInt(page, 10), pageInfo: {}, scope: Object.values(ENVIRONMENTS_SCOPE).includes(scope) @@ -107,6 +112,9 @@ export default { canSetupReviewApp() { return this.environmentApp?.reviewApp?.canSetupReviewApp; }, + canCleanUpEnvs() { + return this.environmentApp?.canStopStaleEnvironments; + }, folders() { return this.environmentApp?.environments?.filter((e) => e.size > 1) ?? []; }, @@ -149,6 +157,19 @@ export default { }, }; }, + openCleanUpEnvsModal() { + if (!this.canCleanUpEnvs) { + return null; + } + + return { + text: this.$options.i18n.cleanUpEnvsButtonLabel, + attributes: { + category: 'secondary', + variant: 'confirm', + }, + }; + }, stoppedCount() { return this.environmentApp?.stoppedCount; }, @@ -178,6 +199,9 @@ export default { showReviewAppModal() { this.isReviewAppModalVisible = true; }, + showCleanUpEnvsModal() { + this.isStopStaleEnvModalVisible = true; + }, setScope(scope) { this.scope = scope; this.moveToPage(1); @@ -219,16 +243,24 @@ export default { :modal-id="$options.modalId" data-testid="enable-review-app-modal" /> + <stop-stale-environments-modal + v-if="canCleanUpEnvs" + v-model="isStopStaleEnvModalVisible" + :modal-id="$options.stopStaleEnvsModalId" + data-testid="stop-stale-environments-modal" + /> <delete-environment-modal :environment="environmentToDelete" graphql /> <stop-environment-modal :environment="environmentToStop" graphql /> <confirm-rollback-modal :environment="environmentToRollback" graphql /> <canary-update-modal :environment="environmentToChangeCanary" :weight="weight" /> <gl-tabs - :action-secondary="addEnvironment" - :action-primary="openReviewAppModal" + :action-secondary="openReviewAppModal" + :action-primary="openCleanUpEnvsModal" + :action-tertiary="addEnvironment" sync-active-tab-with-query-params query-param-name="scope" - @primary="showReviewAppModal" + @secondary="showReviewAppModal" + @primary="showCleanUpEnvsModal" > <gl-tab :query-param-value="$options.ENVIRONMENTS_SCOPE.AVAILABLE" diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue index 9a100e0199e..73dfd993c5b 100644 --- a/app/assets/javascripts/environments/components/new_environment_item.vue +++ b/app/assets/javascripts/environments/components/new_environment_item.vue @@ -323,6 +323,7 @@ export default { > <deployment :deployment="upcomingDeployment" + :visible="visible" :class="{ 'gl-ml-7': inFolder }" class="gl-pl-4" > diff --git a/app/assets/javascripts/environments/components/stop_stale_environments_modal.vue b/app/assets/javascripts/environments/components/stop_stale_environments_modal.vue new file mode 100644 index 00000000000..57873b28d37 --- /dev/null +++ b/app/assets/javascripts/environments/components/stop_stale_environments_modal.vue @@ -0,0 +1,104 @@ +<script> +import { GlTooltipDirective, GlModal, GlDatepicker, GlFormGroup } from '@gitlab/ui'; +import { __, s__ } from '~/locale'; +import { stopStaleEnvironments } from '~/rest_api'; +import { MIN_STALE_ENVIRONMENT_DATE, MAX_STALE_ENVIRONMENT_DATE } from '../constants'; + +export default { + id: 'stop-stale-environments-modal', + name: 'StopStaleEnvironmentsModal', + + components: { + GlModal, + GlDatepicker, + GlFormGroup, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + inject: { + projectId: { + default: '', + }, + }, + model: { + prop: 'visible', + event: 'change', + }, + props: { + modalId: { + type: String, + required: true, + }, + visible: { + type: Boolean, + required: false, + default: false, + }, + }, + modalProps: { + primary: { + text: s__('Environments|Clean up'), + attributes: [{ variant: 'info' }], + }, + cancel: { + text: __('Cancel'), + }, + dateRange: { + minDate: MIN_STALE_ENVIRONMENT_DATE, // 10 years ago + maxDate: MAX_STALE_ENVIRONMENT_DATE, + }, + }, + + data() { + return { + stopEnvironmentsBefore: MAX_STALE_ENVIRONMENT_DATE, + }; + }, + + methods: { + onSubmit() { + stopStaleEnvironments(this.projectId, this.stopEnvironmentsBefore || this.maxDate); + }, + }, +}; +</script> + +<template> + <gl-modal + :action-primary="$options.modalProps.primary" + :action-cancel="$options.modalProps.cancel" + :visible="visible" + :modal-id="modalId" + :title="s__('Environments|Clean up environments')" + static + @primary="onSubmit" + @change="$emit('change', $event)" + > + <p> + {{ + s__( + 'Environments|Select which environments to clean up. \ + Protected environments are excluded. Learn more about cleaning up environments.', + ) + }} + </p> + + <gl-form-group + :label="s__('Environments|Stop unused environments')" + :label-description=" + s__('Environments|Stop environments that have not been updated since the specified date:') + " + label-for="stop_environments-before" + > + <gl-datepicker + v-model="stopEnvironmentsBefore" + input-id="stop-environments-before" + data-testid="stop-environments-before" + :min-date="$options.modalProps.dateRange.minDate" + :max-date="$options.modalProps.dateRange.maxDate" + :default-date="$options.modalProps.dateRange.maxDate" + /> + </gl-form-group> + </gl-modal> +</template> diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js index c4d02da9d21..28424322dd2 100644 --- a/app/assets/javascripts/environments/constants.js +++ b/app/assets/javascripts/environments/constants.js @@ -1,4 +1,5 @@ import { __, s__ } from '~/locale'; +import { getDateInPast } from '~/lib/utils/datetime_utility'; // These statuses are based on how the backend defines pod phases here // lib/gitlab/kubernetes/pod.rb @@ -77,3 +78,12 @@ export const REVIEW_APP_MODAL_I18N = { viewMoreExampleProjects: s__('EnableReviewApp|View more example projects'), copyToClipboardText: s__('EnableReviewApp|Copy snippet'), }; + +export const MIN_STALE_ENVIRONMENT_DATE = getDateInPast(new Date(), 3650); // 10 years ago +export const MAX_STALE_ENVIRONMENT_DATE = getDateInPast(new Date(), 7); // one week ago + +export const ENVIRONMENT_NEW_HELP_TEXT = __( + 'Environments allow you to track deployments of your application.%{linkStart} More information.%{linkEnd}', +); + +export const ENVIRONMENT_EDIT_HELP_TEXT = ENVIRONMENT_NEW_HELP_TEXT; diff --git a/app/assets/javascripts/environments/edit.js b/app/assets/javascripts/environments/edit.js index dd6680f64bd..a128d2fb3c7 100644 --- a/app/assets/javascripts/environments/edit.js +++ b/app/assets/javascripts/environments/edit.js @@ -7,6 +7,7 @@ export default (el) => provide: { projectEnvironmentsPath: el.dataset.projectEnvironmentsPath, updateEnvironmentPath: el.dataset.updateEnvironmentPath, + protectedEnvironmentSettingsPath: el.dataset.protectedEnvironmentSettingsPath, }, render(h) { return h(EditEnvironment, { diff --git a/app/assets/javascripts/environments/environment_details/components/deployment_actions.vue b/app/assets/javascripts/environments/environment_details/components/deployment_actions.vue new file mode 100644 index 00000000000..77d9311743c --- /dev/null +++ b/app/assets/javascripts/environments/environment_details/components/deployment_actions.vue @@ -0,0 +1,31 @@ +<script> +import ActionsComponent from '~/environments/components/environment_actions.vue'; + +export default { + components: { + ActionsComponent, + }, + props: { + actions: { + // actions shape: + /* Array<{ + playable: boolean, + playPath: url, + name: string + scheduledAt: ISO_timestamp | null + }> + */ + type: Array, + required: true, + }, + }, + computed: { + isActionsShown() { + return this.actions.length > 0; + }, + }, +}; +</script> +<template> + <actions-component v-if="isActionsShown" :actions="actions" graphql /> +</template> diff --git a/app/assets/javascripts/environments/environment_details/constants.js b/app/assets/javascripts/environments/environment_details/constants.js index bf690ffedeb..3b33d6a676e 100644 --- a/app/assets/javascripts/environments/environment_details/constants.js +++ b/app/assets/javascripts/environments/environment_details/constants.js @@ -45,6 +45,12 @@ export const ENVIRONMENT_DETAILS_TABLE_FIELDS = [ columnClass: 'gl-w-10p', tdClass: 'gl-vertical-align-middle! gl-white-space-nowrap', }, + { + key: 'actions', + label: __('Actions'), + columnClass: 'gl-w-10p', + tdClass: 'gl-vertical-align-middle! gl-white-space-nowrap', + }, ]; export const translations = { diff --git a/app/assets/javascripts/environments/environment_details/deployments_table.vue b/app/assets/javascripts/environments/environment_details/deployments_table.vue index 41570ee44c0..10f8c06e581 100644 --- a/app/assets/javascripts/environments/environment_details/deployments_table.vue +++ b/app/assets/javascripts/environments/environment_details/deployments_table.vue @@ -5,11 +5,13 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import DeploymentStatusLink from './components/deployment_status_link.vue'; import DeploymentJob from './components/deployment_job.vue'; import DeploymentTriggerer from './components/deployment_triggerer.vue'; +import DeploymentActions from './components/deployment_actions.vue'; import { ENVIRONMENT_DETAILS_TABLE_FIELDS } from './constants'; export default { components: { DeploymentTriggerer, + DeploymentActions, DeploymentJob, Commit, TimeAgoTooltip, @@ -51,5 +53,8 @@ export default { <template #cell(deployed)="{ item }"> <time-ago-tooltip :time="item.deployed" /> </template> + <template #cell(actions)="{ item }"> + <deployment-actions :actions="item.actions" /> + </template> </gl-table-lite> </template> diff --git a/app/assets/javascripts/environments/environment_details/index.vue b/app/assets/javascripts/environments/environment_details/index.vue index b43f4233b9c..f4657c5100a 100644 --- a/app/assets/javascripts/environments/environment_details/index.vue +++ b/app/assets/javascripts/environments/environment_details/index.vue @@ -59,7 +59,11 @@ export default { }, computed: { deployments() { - return this.project.environment?.deployments.nodes.map(convertToDeploymentTableRow) || []; + return ( + this.project.environment?.deployments.nodes.map((deployment) => + convertToDeploymentTableRow(deployment, this.project.environment), + ) || [] + ); }, isLoading() { return this.$apollo.queries.project.loading; diff --git a/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql index 1a572208a1c..7a50ded7d6c 100644 --- a/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql +++ b/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql @@ -4,5 +4,6 @@ query getEnvironmentApp($page: Int, $scope: String, $search: String) { stoppedCount environments reviewApp + canStopStaleEnvironments } } diff --git a/app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql index c6c2024c840..0182b3a7234 100644 --- a/app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql +++ b/app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql @@ -13,6 +13,13 @@ query getEnvironmentDetails( environment(name: $environmentName) { id name + lastDeployment(status: SUCCESS) { + id + job { + id + name + } + } deployments( orderBy: { createdAt: DESC } first: $first @@ -36,6 +43,19 @@ query getEnvironmentDetails( name id webPath + playable + deploymentPipeline: pipeline { + id + jobs(whenExecuted: ["manual"], retried: false) { + nodes { + id + name + playable + scheduledAt + webPath + } + } + } } commit { id diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js index afd56d0cf0d..e21670870b8 100644 --- a/app/assets/javascripts/environments/graphql/resolvers.js +++ b/app/assets/javascripts/environments/graphql/resolvers.js @@ -54,6 +54,7 @@ export const resolvers = (endpoint) => ({ ...convertObjectPropsToCamelCase(res.data.review_app), __typename: 'ReviewApp', }, + canStopStaleEnvironments: res.data.can_stop_stale_environments, stoppedCount: res.data.stopped_count, __typename: 'LocalEnvironmentApp', }; diff --git a/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js b/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js index bfe92fe3125..9802dcbcf78 100644 --- a/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js +++ b/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js @@ -41,22 +41,46 @@ export const getCommitFromDeploymentNode = (deploymentNode) => { }; }; +export const convertJobToDeploymentAction = (job) => { + return { + name: job.name, + playable: job.playable, + scheduledAt: job.scheduledAt, + playPath: `${job.webPath}/play`, + }; +}; + +export const getActionsFromDeploymentNode = (deploymentNode, lastDeploymentName) => { + if (!deploymentNode || !lastDeploymentName) { + return []; + } + + return ( + deploymentNode.job?.deploymentPipeline?.jobs?.nodes + ?.filter((deployment) => deployment.name !== lastDeploymentName) + .map(convertJobToDeploymentAction) || [] + ); +}; + /** * This function transforms deploymentNode object coming from GraphQL to object compatible with app/assets/javascripts/environments/environment_details/page.vue table * @param {Object} deploymentNode * @returns {Object} */ -export const convertToDeploymentTableRow = (deploymentNode) => { +export const convertToDeploymentTableRow = (deploymentNode, environment) => { + const { lastDeployment } = environment; + const commit = getCommitFromDeploymentNode(deploymentNode); return { status: deploymentNode.status.toLowerCase(), id: deploymentNode.iid, triggerer: deploymentNode.triggerer, - commit: getCommitFromDeploymentNode(deploymentNode), + commit, job: deploymentNode.job && { webPath: deploymentNode.job.webPath, label: `${deploymentNode.job.name} (#${getIdFromGraphQLId(deploymentNode.job.id)})`, }, created: deploymentNode.createdAt || '', deployed: deploymentNode.finishedAt || '', + actions: getActionsFromDeploymentNode(deploymentNode, lastDeployment?.job?.name), }; }; |