List the deployments related to a release
Release notes
As GitLab offers an integrated platform for the whole DevSecOps domain, it was possible for a long time to create a release and register a deployment from a git tag. At the same time, most of these objects were not visually connected on the GitLab UI. Since GitLab 17.6 the deployment pages of tagged deployments show the related release's notes on the deployment within an environment. Recently, we added another integration point, showing the list of related deployments on the release pages. This allows release managers to see where a specific release was already deployed or is waiting to be deployed.
We would like to express our gratitude to Anton Kalmykov for contributing both features to GitLab.
Problem to solve
As a release manager, I want to know where the current release was already deployed to, and quickly reach related deployment approval information.
Proposal
Add a section similarly to Assets
It might be better to redesign the page and move the Release notes, Assets and Deployments into a tab-based setup, instead of the accordions at the top.
The deployment list should show all the deployments related to the git tag of the release. Columns and information to share:
Environment | Status | Deployment ID | Commit | Triggerer | Created | Deployed |
---|---|---|---|---|---|---|
Environment name linked to the specific environment | Status badge (no link) | The ID of the deployment linked to the deployment details page | Commit related informations: message, branch, sha | User who triggered the related pipeline | Created datetime of the deployment job | Finished datetime of the deployment job (if available, shown for non-successful jobs too) |
The deployment details should be visible only for Reporter
role and up.
Design proposal
Implementation guide
- Prepare the related deployments data in the
release
model:
def related_deployments
Deployment
.with(Gitlab::SQL::CTE.new(:available_environments, project.environments.available.select(:id)).to_arel)
.where('environment_id IN (SELECT * FROM available_environments)')
.where(ref: tag)
.with_environment_page_associations
end
-
Use the data in the
release_helper
.-
create a helper method:
def deployments_for_release project = @release.project commit = project.repository.commit(@release.tag) deployments = @release.related_deployments deployments.map do |deployment| user = deployment.deployable.user environment = deployment.environment { environment: { name: environment&.name, url: environment ? project_environment_url(project, environment) : nil }, status: deployment.status, deployment: { id: deployment.id, url: project_environment_deployment_path(project, environment, deployment) }, commit: { sha: commit.id, name: commit.author_name, commit_url: project_commit_url(deployment.project, commit), short_sha: commit.short_id, title: commit.title }, triggerer: { name: user&.name, web_url: user ? user_url(user) : nil, avatar_url: user&.avatar_url }, created_at: deployment.created_at, finished_at: deployment.finished_at } end end
-
provide this data to the frontend by updating
data_for_show_page
method:def data_for_show_page { project_id: @project.id, project_path: @project.full_path, tag_name: @release.tag, deployments: deployments_for_release.to_json } end
-
-
Process the newly added data on the frontend by updating the
mount_show
file.diff --git a/app/assets/javascripts/releases/mount_show.js b/app/assets/javascripts/releases/mount_show.js index 5e0bce404bb0..87a795859f34 100644 --- a/app/assets/javascripts/releases/mount_show.js +++ b/app/assets/javascripts/releases/mount_show.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import ReleaseShowApp from './components/app_show.vue'; Vue.use(VueApollo); @@ -14,7 +15,7 @@ export default () => { if (!el) return false; - const { projectPath, tagName } = el.dataset; + const { projectPath, tagName, deployments } = el.dataset; return new Vue({ el, @@ -23,6 +24,11 @@ export default () => { projectPath, tagName, }, - render: (h) => h(ReleaseShowApp), + render: (h) => + h(ReleaseShowApp, { + props: { + deployments: convertObjectPropsToCamelCase(JSON.parse(deployments), { deep: true }), + }, + }), }); };
Within this change, we are reading the new
deployments
field. We need to process it from JSON and convert properties to camelCase to conform with other instances on the frontend. -
Create a new component to display the related deployments -
release_block_deployments.vue
-
Provide
deployments
prop to the newly added component and render the component only when the deployments list is present:diff --git a/app/assets/javascripts/releases/components/app_show.vue b/app/assets/javascripts/releases/components/app_show.vue index dd2d35431b7e..631c92fe2b70 100644 --- a/app/assets/javascripts/releases/components/app_show.vue +++ b/app/assets/javascripts/releases/components/app_show.vue @@ -21,6 +21,13 @@ export default { default: '', }, }, + props: { + deployments: { + type: Array, + required: false, + default: () => [], + }, + }, apollo: { // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties release: { @@ -71,6 +78,6 @@ export default { <div class="gl-mt-3"> <release-skeleton-loader v-if="$apollo.queries.release.loading" /> - <release-block v-else-if="release" :release="release" /> + <release-block v-else-if="release" :release="release" :deployments="deployments" /> </div> </template> diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue index 3183434aa17b..20700a1f5e3f 100644 --- a/app/assets/javascripts/releases/components/release_block.vue +++ b/app/assets/javascripts/releases/components/release_block.vue @@ -15,6 +15,7 @@ import ReleaseBlockAssets from './release_block_assets.vue'; import ReleaseBlockFooter from './release_block_footer.vue'; import ReleaseBlockTitle from './release_block_title.vue'; import ReleaseBlockMilestoneInfo from './release_block_milestone_info.vue'; +import ReleaseBlockDeployments from './release_block_deployments.vue'; export default { name: 'ReleaseBlock', @@ -26,6 +27,7 @@ export default { ReleaseBlockFooter, ReleaseBlockTitle, ReleaseBlockMilestoneInfo, + ReleaseBlockDeployments, }, directives: { SafeHtml, @@ -42,6 +44,11 @@ export default { required: false, default: CREATED_ASC, }, + deployments: { + type: Array, + required: false, + default: () => [], + }, }, data() { return { @@ -136,7 +143,7 @@ export default { </gl-button> </template> - <div class="gl-flex gl-flex-col gl-gap-5"> + <div class="gl-mx-5 gl-my-4 gl-flex gl-flex-col gl-gap-5"> <div v-if="shouldRenderMilestoneInfo" class="gl-border-b-1 gl-border-gray-100 gl-border-b-solid" @@ -152,6 +159,15 @@ export default { /> </div> + <release-block-deployments + v-if="deployments.length" + :class="{ + 'gl-border-b-1 gl-border-gray-100 gl-pb-5 gl-border-b-solid': + shouldRenderAssets || hasEvidence || release.descriptionHtml, + }" + :deployments="deployments" + /> + <release-block-assets v-if="shouldRenderAssets" :assets="assets"
-
Here's an example of how to display the data on the release page. It uses the GlTable similarly to how it's done on the environment page. Feel free to adjust the code as you see fit:
<script> import { GlLink, GlButton, GlCollapse, GlIcon, GlBadge, GlTableLite } from '@gitlab/ui'; import Commit from '~/vue_shared/components/commit.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import DeploymentStatusLink from '~/environments/components/deployment_status_link.vue'; import DeploymentTriggerer from '~/environments/environment_details/components/deployment_triggerer.vue'; import { __ } from '~/locale'; export default { name: 'ReleaseBlockDeployments', components: { GlLink, GlButton, GlCollapse, GlIcon, GlBadge, GlTableLite, TimeAgoTooltip, Commit, DeploymentStatusLink, DeploymentTriggerer, }, props: { deployments: { type: Array, required: true, }, }, data() { return { isDeploymentsExpanded: true, }; }, methods: { toggleDeploymentsExpansion() { this.isDeploymentsExpanded = !this.isDeploymentsExpanded; }, }, tableFields: [ { key: 'environment', label: __('Environment'), tdClass: '!gl-align-middle', thClass: '!gl-border-t-0', }, { key: 'status', label: __('Status'), tdClass: '!gl-align-middle', thClass: '!gl-border-t-0', }, { key: 'deploymentId', label: __('Deployment ID'), tdClass: '!gl-align-middle', thClass: '!gl-border-t-0', }, { key: 'commit', label: __('Commit'), tdClass: '!gl-align-middle', thClass: '!gl-border-t-0', }, { key: 'triggerer', label: __('Triggerer'), tdClass: '!gl-align-middle', thClass: '!gl-border-t-0', }, { key: 'created', label: __('Created'), tdClass: '!gl-align-middle gl-whitespace-nowrap', thClass: '!gl-border-t-0', }, { key: 'finished', label: __('Finished'), tdClass: '!gl-align-middle gl-whitespace-nowrap', thClass: '!gl-border-t-0', }, ], }; </script> <template> <div> <gl-button data-testid="accordion-button" variant="link" class="!gl-text-default" button-text-classes="gl-heading-5" @click="toggleDeploymentsExpansion" > <gl-icon name="chevron-right" class="gl-transition-all" :class="{ 'gl-rotate-90': isDeploymentsExpanded }" /> {{ __('Deployments') }} <gl-badge variant="neutral" class="gl-inline-block">{{ deployments.length }}</gl-badge> </gl-button> <gl-collapse v-model="isDeploymentsExpanded"> <div class="gl-pl-6 gl-pt-3"> <gl-table-lite :items="deployments" :fields="$options.tableFields" stacked="lg"> <template #cell(environment)="{ item }"> <gl-link :href="item.environment.url"> {{ item.environment.name }} </gl-link> </template> <template #cell(status)="{ item }"> <deployment-status-link :deployment="item" :status="item.status" /> </template> <template #cell(deploymentId)="{ item }"> <gl-link :href="item.deployment.url"> {{ item.deployment.id }} </gl-link> </template> <template #cell(triggerer)="{ item }"> <deployment-triggerer :triggerer="item.triggerer" /> </template> <template #cell(commit)="{ item }"> <commit :short-sha="item.commit.shortSha" :commit-url="item.commit.commitUrl" :title="item.commit.title" :author="item.commit.author" :show-ref-info="false" /> </template> <template #cell(created)="{ item }"> <time-ago-tooltip :time="item.createdAt" enable-truncation data-testid="deployment-created-at" /> </template> <template #cell(finished)="{ item }"> <time-ago-tooltip v-if="item.finishedAt" :time="item.finishedAt" enable-truncation data-testid="deployment-finished-at" /> </template> </gl-table-lite> </div> </gl-collapse> </div> </template>
-
As the new section is now the first one and should be expanded by default, we should update the
release_block_assets
section to be collapsed by default:diff --git a/app/assets/javascripts/releases/components/release_block_assets.vue b/app/assets/javascripts/releases/components/release_block_assets.vue index 3953a3990c29..8f95c77f16b1 100644 --- a/app/assets/javascripts/releases/components/release_block_assets.vue +++ b/app/assets/javascripts/releases/components/release_block_assets.vue @@ -24,7 +24,7 @@ export default { }, data() { return { - isAssetsExpanded: true, + isAssetsExpanded: false, }; }, computed: {
-
Update the corresponding tests:
- Javascript tests at
spec/frontend/releases/components/
- Ruby tests at
spec/helpers/releases_helper_spec.rb
andspec/models/release_spec.rb
- Javascript tests at
-
Update the translations by running
tooling/bin/gettext_extractor locale/gitlab.pot
Implementation guide for adding internal events tracking
As the feature-related changes should affect any files, I suggest adding events and metrics in a separate MR.
- Follow the documentation here
- Run
scripts/internal_events/cli.rb
and create 4 events using the following options:-
- New Event -- track when a specific scenario occurs on gitlab instances ex) a user applies a label to an issue
-
Event description | Event name | Event identifiers | Additional property | Group | Tiers |
---|---|---|---|---|---|
User opens the deployments section on the release page | click_expand_deployments_on_release_page |
[namespace, project, user] |
None | cd:deploy:environments |
free, premium, ultimate |
User opens the assets section on the release page | click_expand_assets_on_release_page |
[namespace, project, user] |
None | cd:deploy:environments |
free, premium, ultimate |
User clicks on the environment link | click_environment_link_on_release_page |
[namespace, project, user] |
None | cd:deploy:environments |
free, premium, ultimate |
User clicks on the deployment link | click_deployment_link_on_release_page |
[namespace, project, user] |
None | cd:deploy:environments |
free, premium, ultimate |
-
- Save & Create Metric
- Monthly/Weekly count of unique users who triggered <event_name>
- Use the event description to create a metric description
- To trigger this events follow this guide
Intended users
Feature Usage Metrics
- MAU of user clicks on the tab/accordion (for every option)
- MAU of user clicks on the environment links
- MAU of user clicks on the deployment ID links
Does this feature require an audit event?
no