Commit e2763f34 authored by Anna Vovchenko's avatar Anna Vovchenko Committed by Olena Horal-Koretska

Upsell the GitLab Managed Terraform state if the repo contains `.tf` files

Changelog: added
parent 8de2fc1e
import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
import initTerraformNotification from '../../projects/terraform_notification';
import { initSidebarTracking } from '../shared/nav/sidebar_tracking';
import Project from './project';
new Project(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
initSidebarTracking();
initTerraformNotification();
<script>
import { GlBanner } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import { parseBoolean, setCookie, getCookie } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
export default {
name: 'TerraformNotification',
i18n: {
title: s__('TerraformBanner|Using Terraform? Try the GitLab Managed Terraform State'),
description: s__(
'TerraformBanner|The GitLab managed Terraform state backend can store your Terraform state easily and securely, and spares you from setting up additional remote resources. Its features include: versioning, encryption of the state file both in transit and at rest, locking, and remote Terraform plan/apply execution.',
),
buttonText: s__("TerraformBanner|Learn more about GitLab's Backend State"),
},
components: {
GlBanner,
},
props: {
projectId: {
type: Number,
required: true,
},
},
data() {
return {
isVisible: true,
};
},
computed: {
bannerDissmisedKey() {
return `terraform_notification_dismissed_for_project_${this.projectId}`;
},
docsUrl() {
return helpPagePath('user/infrastructure/terraform_state');
},
},
created() {
if (parseBoolean(getCookie(this.bannerDissmisedKey))) {
this.isVisible = false;
}
},
methods: {
handleClose() {
setCookie(this.bannerDissmisedKey, true);
this.isVisible = false;
},
},
};
</script>
<template>
<div v-if="isVisible">
<div class="gl-py-5">
<gl-banner
:title="$options.i18n.title"
:button-text="$options.i18n.buttonText"
:button-link="docsUrl"
variant="introduction"
@close="handleClose"
>
<p>{{ $options.i18n.description }}</p>
</gl-banner>
</div>
</div>
</template>
import Vue from 'vue';
import TerraformNotification from './components/terraform_notification.vue';
export default () => {
const el = document.querySelector('.js-terraform-notification');
if (!el) {
return false;
}
const { projectId } = el.dataset;
return new Vue({
el,
render: (createElement) =>
createElement(TerraformNotification, { props: { projectId: Number(projectId) } }),
});
};
......@@ -350,6 +350,10 @@ module ProjectsHelper
nil
end
def show_terraform_banner?(project)
project.repository_languages.with_programming_language('HCL').exists? && project.terraform_states.empty?
end
private
def tab_ability_map
......
......@@ -8,6 +8,10 @@ class RepositoryLanguage < ApplicationRecord
default_scope { includes(:programming_language) } # rubocop:disable Cop/DefaultScope
scope :with_programming_language, ->(name) do
joins(:programming_language).merge(ProgrammingLanguage.with_name_case_insensitive(name))
end
validates :project, presence: true
validates :share, inclusion: { in: 0..100, message: "The share of a language is between 0 and 100" }
validates :programming_language, uniqueness: { scope: :project_id }
......
......@@ -9,3 +9,4 @@
= render 'shared/auto_devops_implicitly_enabled_banner', project: project
= render_if_exists 'projects/above_size_limit_warning', project: project
= render_if_exists 'shared/shared_runners_minutes_limit', project: project, classes: [container_class, ("limit-container-width" unless fluid_layout)]
= render_if_exists 'projects/terraform_banner', project: project
- @content_class = "container-limited limit-container-width" unless fluid_layout
- if show_terraform_banner?(project)
.container-fluid{ class: @content_class }
.js-terraform-notification{ data: { project_id: project.id } }
......@@ -32193,6 +32193,15 @@ msgstr ""
msgid "Terraform"
msgstr ""
msgid "TerraformBanner|Learn more about GitLab's Backend State"
msgstr ""
msgid "TerraformBanner|The GitLab managed Terraform state backend can store your Terraform state easily and securely, and spares you from setting up additional remote resources. Its features include: versioning, encryption of the state file both in transit and at rest, locking, and remote Terraform plan/apply execution."
msgstr ""
msgid "TerraformBanner|Using Terraform? Try the GitLab Managed Terraform State"
msgstr ""
msgid "Terraform|%{name} successfully removed"
msgstr ""
......
import { GlBanner } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { setCookie, parseBoolean } from '~/lib/utils/common_utils';
import TerraformNotification from '~/projects/terraform_notification/components/terraform_notification.vue';
jest.mock('~/lib/utils/common_utils');
const bannerDissmisedKey = 'terraform_notification_dismissed_for_project_1';
describe('TerraformNotificationBanner', () => {
let wrapper;
const propsData = {
projectId: 1,
};
const findBanner = () => wrapper.findComponent(GlBanner);
beforeEach(() => {
wrapper = shallowMount(TerraformNotification, {
propsData,
stubs: { GlBanner },
});
});
afterEach(() => {
wrapper.destroy();
parseBoolean.mockReturnValue(false);
});
describe('when the dismiss cookie is set', () => {
beforeEach(() => {
parseBoolean.mockReturnValue(true);
wrapper = shallowMount(TerraformNotification, {
propsData,
});
});
it('should not render the banner', () => {
expect(findBanner().exists()).toBe(false);
});
});
describe('when the dismiss cookie is not set', () => {
it('should render the banner', () => {
expect(findBanner().exists()).toBe(true);
});
});
describe('when close button is clicked', () => {
beforeEach(async () => {
await findBanner().vm.$emit('close');
});
it('should set the cookie with the bannerDissmisedKey', () => {
expect(setCookie).toHaveBeenCalledWith(bannerDissmisedKey, true);
});
it('should remove the banner', () => {
expect(findBanner().exists()).toBe(false);
});
});
});
......@@ -876,6 +876,37 @@ RSpec.describe ProjectsHelper do
end
end
describe '#show_terraform_banner?' do
let_it_be(:ruby) { create(:programming_language, name: 'Ruby') }
let_it_be(:hcl) { create(:programming_language, name: 'HCL') }
subject { helper.show_terraform_banner?(project) }
before do
create(:repository_language, project: project, programming_language: language, share: 1)
end
context 'the project does not contain terraform files' do
let(:language) { ruby }
it { is_expected.to be_falsey }
end
context 'the project contains terraform files' do
let(:language) { hcl }
it { is_expected.to be_truthy }
context 'the project already has a terraform state' do
before do
create(:terraform_state, project: project)
end
it { is_expected.to be_falsey }
end
end
end
describe '#project_title' do
subject { helper.project_title(project) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'projects/_flash_messages' do
let_it_be(:template) { 'projects/flash_messages' }
let_it_be(:user) { create(:user) }
let_it_be(:ruby) { create(:programming_language, name: 'Ruby') }
let_it_be(:html) { create(:programming_language, name: 'HTML') }
let_it_be(:hcl) { create(:programming_language, name: 'HCL') }
before do
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:can?).with(user, :download_code, project).and_return(true)
end
context 'when current_user has download_code permission' do
context 'when user has a terraform state' do
let_it_be(:project) { create(:project) }
let_it_be(:terraform_state) { create(:terraform_state, :locked, :with_version, project: project) }
it "doesn't show the terraform notification banner" do
render(template, project: project)
expect(view.content_for(:flash_message)).not_to have_selector('.js-terraform-notification')
end
end
context 'when there are no .tf files in the repository' do
let_it_be(:project) { create(:project) }
let_it_be(:mock_repo_languages) do
{ project => { ruby => 0.5, html => 0.5 } }
end
before do
mock_repo_languages.each do |project, lang_shares|
lang_shares.each do |lang, share|
create(:repository_language, project: project, programming_language: lang, share: share)
end
end
end
it "doesn't show the terraform notification banner" do
render(template, project: project)
expect(view.content_for(:flash_message)).not_to have_selector('.js-terraform-notification')
end
end
context 'when .tf files are present in the repository and user does not have any terraform states' do
let_it_be(:project) { create(:project) }
let_it_be(:mock_repo_languages) do
{ project => { ruby => 0.5, hcl => 0.5 } }
end
before do
mock_repo_languages.each do |project, lang_shares|
lang_shares.each do |lang, share|
create(:repository_language, project: project, programming_language: lang, share: share)
end
end
end
it 'shows the terraform notification banner' do
render(template, project: project)
expect(view.content_for(:flash_message)).to have_selector('.js-terraform-notification')
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment