diff options
Diffstat (limited to 'app/assets/javascripts/environments')
6 files changed, 305 insertions, 2 deletions
diff --git a/app/assets/javascripts/environments/components/deploy_board.vue b/app/assets/javascripts/environments/components/deploy_board.vue index f22a0705b3d..31bc462f0b9 100644 --- a/app/assets/javascripts/environments/components/deploy_board.vue +++ b/app/assets/javascripts/environments/components/deploy_board.vue @@ -15,10 +15,10 @@ import { GlLink, GlTooltip, GlTooltipDirective, - GlSafeHtmlDirective as SafeHtml, GlSprintf, } from '@gitlab/ui'; import { isEmpty } from 'lodash'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import { s__, n__ } from '~/locale'; import InstanceComponent from '~/vue_shared/components/deployment_instance.vue'; import { STATUS_MAP, CANARY_STATUS } from '../constants'; diff --git a/app/assets/javascripts/environments/environment_details/constants.js b/app/assets/javascripts/environments/environment_details/constants.js new file mode 100644 index 00000000000..56c70c354b7 --- /dev/null +++ b/app/assets/javascripts/environments/environment_details/constants.js @@ -0,0 +1,47 @@ +import { __ } from '~/locale'; + +export const ENVIRONMENT_DETAILS_PAGE_SIZE = 20; +export const ENVIRONMENT_DETAILS_TABLE_FIELDS = [ + { + key: 'status', + label: __('Status'), + columnClass: 'gl-w-10p', + tdClass: 'gl-vertical-align-middle!', + }, + { + key: 'id', + label: __('ID'), + columnClass: 'gl-w-5p', + tdClass: 'gl-vertical-align-middle!', + }, + { + key: 'triggerer', + label: __('Triggerer'), + columnClass: 'gl-w-10p', + tdClass: 'gl-vertical-align-middle!', + }, + { + key: 'commit', + label: __('Commit'), + columnClass: 'gl-w-20p', + tdClass: 'gl-vertical-align-middle!', + }, + { + key: 'job', + label: __('Job'), + columnClass: 'gl-w-20p', + tdClass: 'gl-vertical-align-middle!', + }, + { + key: 'created', + label: __('Created'), + columnClass: 'gl-w-10p', + tdClass: 'gl-vertical-align-middle! gl-white-space-nowrap', + }, + { + key: 'deployed', + label: __('Deployed'), + columnClass: 'gl-w-10p', + tdClass: 'gl-vertical-align-middle! gl-white-space-nowrap', + }, +]; diff --git a/app/assets/javascripts/environments/environment_details/index.vue b/app/assets/javascripts/environments/environment_details/index.vue new file mode 100644 index 00000000000..435d3fd820e --- /dev/null +++ b/app/assets/javascripts/environments/environment_details/index.vue @@ -0,0 +1,118 @@ +<script> +import { + GlTableLite, + GlAvatarLink, + GlAvatar, + GlLink, + GlTooltipDirective, + GlTruncate, + GlBadge, + GlLoadingIcon, +} from '@gitlab/ui'; +import Commit from '~/vue_shared/components/commit.vue'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import environmentDetailsQuery from '../graphql/queries/environment_details.query.graphql'; +import { convertToDeploymentTableRow } from '../helpers/deployment_data_transformation_helper'; +import DeploymentStatusBadge from '../components/deployment_status_badge.vue'; +import { ENVIRONMENT_DETAILS_PAGE_SIZE, ENVIRONMENT_DETAILS_TABLE_FIELDS } from './constants'; + +export default { + components: { + GlLoadingIcon, + GlBadge, + DeploymentStatusBadge, + TimeAgoTooltip, + GlTableLite, + GlAvatarLink, + GlAvatar, + GlLink, + GlTruncate, + Commit, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + projectFullPath: { + type: String, + required: true, + }, + environmentName: { + type: String, + required: true, + }, + }, + apollo: { + project: { + query: environmentDetailsQuery, + variables() { + return { + projectFullPath: this.projectFullPath, + environmentName: this.environmentName, + pageSize: ENVIRONMENT_DETAILS_PAGE_SIZE, + }; + }, + }, + }, + data() { + return { + project: { + loading: true, + }, + loading: 0, + tableFields: ENVIRONMENT_DETAILS_TABLE_FIELDS, + }; + }, + computed: { + deployments() { + return this.project.environment?.deployments.nodes.map(convertToDeploymentTableRow) || []; + }, + isLoading() { + return this.$apollo.queries.project.loading; + }, + }, +}; +</script> +<template> + <div> + <gl-loading-icon v-if="isLoading" size="lg" class="mt-3" /> + <gl-table-lite v-else :items="deployments" :fields="tableFields" fixed stacked="lg"> + <template #table-colgroup="{ fields }"> + <col v-for="field in fields" :key="field.key" :class="field.columnClass" /> + </template> + <template #cell(status)="{ item }"> + <div> + <deployment-status-badge :status="item.status" /> + </div> + </template> + <template #cell(id)="{ item }"> + <strong>{{ item.id }}</strong> + </template> + <template #cell(triggerer)="{ item }"> + <gl-avatar-link :href="item.triggerer.webUrl"> + <gl-avatar + v-gl-tooltip + :title="item.triggerer.name" + :src="item.triggerer.avatarUrl" + :size="24" + /> + </gl-avatar-link> + </template> + <template #cell(commit)="{ item }"> + <commit v-bind="item.commit" /> + </template> + <template #cell(job)="{ item }"> + <gl-link v-if="item.job" :href="item.job.webPath"> + <gl-truncate :text="item.job.label" /> + </gl-link> + <gl-badge v-else variant="info">{{ __('API') }}</gl-badge> + </template> + <template #cell(created)="{ item }"> + <time-ago-tooltip :time="item.created" /> + </template> + <template #cell(deployed)="{ item }"> + <time-ago-tooltip :time="item.deployed" /> + </template> + </gl-table-lite> + </div> +</template> diff --git a/app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql new file mode 100644 index 00000000000..e8f2a2cdf7f --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/environment_details.query.graphql @@ -0,0 +1,48 @@ +query getEnvironmentDetails($projectFullPath: ID!, $environmentName: String, $pageSize: Int) { + project(fullPath: $projectFullPath) { + id + name + fullPath + environment(name: $environmentName) { + id + name + deployments(orderBy: { createdAt: DESC }, first: $pageSize) { + nodes { + id + iid + status + ref + tag + job { + name + id + webPath + } + commit { + id + shortId + message + webUrl + authorGravatar + authorName + authorEmail + author { + id + name + avatarUrl + webUrl + } + } + triggerer { + id + webUrl + name + avatarUrl + } + createdAt + finishedAt + } + } + } + } +} diff --git a/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js b/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js new file mode 100644 index 00000000000..bfe92fe3125 --- /dev/null +++ b/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js @@ -0,0 +1,62 @@ +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; + +/** + * This function transforms Commit object coming from GraphQL to object compatible with app/assets/javascripts/vue_shared/components/commit.vue author object + * @param {Object} Commit + * @returns {Object} + */ +export const getAuthorFromCommit = (commit) => { + if (commit.author) { + return { + username: commit.author.name, + path: commit.author.webUrl, + avatar_url: commit.author.avatarUrl, + }; + } + return { + username: commit.authorName, + path: `mailto:${commit.authorEmail}`, + avatar_url: commit.authorGravatar, + }; +}; + +/** + * This function transforms deploymentNode object coming from GraphQL to object compatible with app/assets/javascripts/vue_shared/components/commit.vue + * @param {Object} deploymentNode + * @returns {Object} + */ +export const getCommitFromDeploymentNode = (deploymentNode) => { + if (!deploymentNode.commit) { + throw new Error("deploymentNode argument doesn't have 'commit' field", deploymentNode); + } + return { + title: deploymentNode.commit.message, + commitUrl: deploymentNode.commit.webUrl, + shortSha: deploymentNode.commit.shortId, + tag: deploymentNode.tag, + commitRef: { + name: deploymentNode.ref, + }, + author: getAuthorFromCommit(deploymentNode.commit), + }; +}; + +/** + * 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) => { + return { + status: deploymentNode.status.toLowerCase(), + id: deploymentNode.iid, + triggerer: deploymentNode.triggerer, + commit: getCommitFromDeploymentNode(deploymentNode), + job: deploymentNode.job && { + webPath: deploymentNode.job.webPath, + label: `${deploymentNode.job.name} (#${getIdFromGraphQLId(deploymentNode.job.id)})`, + }, + created: deploymentNode.createdAt || '', + deployed: deploymentNode.finishedAt || '', + }; +}; diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js index 6df4fad83f2..ba816599ac2 100644 --- a/app/assets/javascripts/environments/mount_show.js +++ b/app/assets/javascripts/environments/mount_show.js @@ -1,6 +1,8 @@ import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import EnvironmentsDetailHeader from './components/environments_detail_header.vue'; +import { apolloProvider } from './graphql/client'; import environmentsMixin from './mixins/environments_mixin'; export const initHeader = () => { @@ -41,7 +43,33 @@ export const initHeader = () => { cancelAutoStopPath: dataset.environmentCancelAutoStopPath, terminalPath: dataset.environmentTerminalPath, metricsPath: dataset.environmentMetricsPath, - updatePath: dataset.environmentEditPath, + updatePath: dataset.tnvironmentEditPath, + }, + }); + }, + }); +}; + +export const initPage = async () => { + if (!gon.features.environmentDetailsVue) { + return null; + } + const EnvironmentsDetailPageModule = await import('./environment_details/index.vue'); + const EnvironmentsDetailPage = EnvironmentsDetailPageModule.default; + const dataElement = document.getElementById('environments-detail-view'); + const dataSet = convertObjectPropsToCamelCase(JSON.parse(dataElement.dataset.details)); + + Vue.use(VueApollo); + const el = document.getElementById('environment_details_page'); + return new Vue({ + el, + apolloProvider: apolloProvider(), + provide: {}, + render(createElement) { + return createElement(EnvironmentsDetailPage, { + props: { + projectFullPath: dataSet.projectFullPath, + environmentName: dataSet.name, }, }); }, |