Commit 1cace160 authored by Thong Kuah's avatar Thong Kuah

Merge branch '273592-download-action' into 'master'

Add download action to the Terraform state listing

See merge request gitlab-org/gitlab!48837
parents 411b8015 00b5b22f
<script>
import { GlBadge, GlIcon, GlSprintf, GlTable, GlTooltip } from '@gitlab/ui';
import { s__ } from '~/locale';
import StateActions from './states_table_actions.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
......@@ -11,6 +12,7 @@ export default {
GlSprintf,
GlTable,
GlTooltip,
StateActions,
TimeAgoTooltip,
},
mixins: [timeagoMixin],
......@@ -19,10 +21,15 @@ export default {
required: true,
type: Array,
},
terraformAdmin: {
required: false,
type: Boolean,
default: false,
},
},
computed: {
fields() {
return [
const columns = [
{
key: 'name',
thClass: 'gl-display-none',
......@@ -33,6 +40,16 @@ export default {
tdClass: 'gl-text-right',
},
];
if (this.terraformAdmin) {
columns.push({
key: 'actions',
thClass: 'gl-display-none',
tdClass: 'gl-w-10',
});
}
return columns;
},
},
methods: {
......@@ -97,5 +114,9 @@ export default {
</gl-sprintf>
</p>
</template>
<template v-if="terraformAdmin" #cell(actions)="{ item }">
<state-actions :state="item" />
</template>
</gl-table>
</template>
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
components: {
GlDropdown,
GlDropdownItem,
},
props: {
state: {
required: true,
type: Object,
},
},
i18n: {
downloadJSON: s__('Terraform|Download JSON'),
},
};
</script>
<template>
<div v-if="state.latestVersion">
<gl-dropdown icon="ellipsis_v" right :data-testid="`terraform-state-actions-${state.name}`">
<gl-dropdown-item
data-testid="terraform-state-download"
:download="`${state.name}.json`"
:href="state.latestVersion.downloadPath"
>
{{ $options.i18n.downloadJSON }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</template>
......@@ -46,6 +46,11 @@ export default {
required: true,
type: String,
},
terraformAdmin: {
required: false,
type: Boolean,
default: false,
},
},
data() {
return {
......@@ -111,7 +116,7 @@ export default {
<div v-else-if="statesList">
<div v-if="statesCount">
<states-table :states="statesList" />
<states-table :states="statesList" :terraform-admin="terraformAdmin" />
<div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-keyset-pagination
......
#import "~/graphql_shared/fragments/user.fragment.graphql"
fragment StateVersion on TerraformStateVersion {
downloadPath
serial
updatedAt
createdByUser {
......
......@@ -24,6 +24,7 @@ export default () => {
props: {
emptyStateImage,
projectPath,
terraformAdmin: el.hasAttribute('data-terraform-admin'),
},
});
},
......
# frozen_string_literal: true
module Projects::TerraformHelper
def js_terraform_list_data(project)
def js_terraform_list_data(current_user, project)
{
empty_state_image: image_path('illustrations/empty-state/empty-serverless-lg.svg'),
project_path: project.full_path
project_path: project.full_path,
terraform_admin: current_user&.can?(:admin_terraform_state, project)
}
end
end
- breadcrumb_title _('Terraform')
- page_title _('Terraform')
#js-terraform-list{ data: js_terraform_list_data(@project) }
#js-terraform-list{ data: js_terraform_list_data(current_user, @project) }
---
title: Add download action to the Terraform state listing
merge_request: 48837
author:
type: added
......@@ -26946,6 +26946,9 @@ msgstr ""
msgid "Terraform|An error occurred while loading your Terraform States"
msgstr ""
msgid "Terraform|Download JSON"
msgstr ""
msgid "Terraform|Find out how to use the %{linkStart}GitLab managed Terraform State%{linkEnd}"
msgstr ""
......
......@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Projects::TerraformController do
let_it_be(:project) { create(:project) }
let_it_be(:project) { create(:project, :public) }
describe 'GET index' do
subject { get :index, params: { namespace_id: project.namespace, project_id: project } }
......@@ -34,5 +34,15 @@ RSpec.describe Projects::TerraformController do
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when no user is present' do
before do
subject
end
it 'shows 404' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
......@@ -4,44 +4,76 @@ require 'spec_helper'
RSpec.describe 'Terraform', :js do
let_it_be(:project) { create(:project) }
let_it_be(:terraform_state) { create(:terraform_state, :locked, :with_version, project: project) }
let(:user) { project.creator }
context 'when user is a terraform administrator' do
let(:admin) { project.creator }
before do
gitlab_sign_in(user)
end
context 'when user does not have any terraform states and visits index page' do
before do
visit project_terraform_index_path(project)
gitlab_sign_in(admin)
end
it 'sees an empty state' do
expect(page).to have_content('Get started with Terraform')
end
end
context 'when user has a terraform state' do
let_it_be(:terraform_state) { create(:terraform_state, :locked, project: project) }
context 'when user does not have any terraform states and visits the index page' do
let(:empty_project) { create(:project) }
context 'when user visits the index page' do
before do
visit project_terraform_index_path(project)
empty_project.add_maintainer(admin)
visit project_terraform_index_path(empty_project)
end
it 'displays a tab with states count' do
expect(page).to have_content("States #{project.terraform_states.size}")
it 'sees an empty state' do
expect(page).to have_content('Get started with Terraform')
end
end
context 'when user has a terraform state' do
context 'when user visits the index page' do
before do
visit project_terraform_index_path(project)
end
it 'displays a tab with states count' do
expect(page).to have_content("States #{project.terraform_states.size}")
end
it 'displays a table with terraform states' do
expect(page).to have_selector(
'[data-testid="terraform-states-table-name"]',
count: project.terraform_states.size
)
end
it 'displays terraform actions dropdown' do
expect(page).to have_selector(
'[data-testid*="terraform-state-actions"]',
count: project.terraform_states.size
)
end
it 'displays a table with terraform states' do
it 'displays terraform information' do
expect(page).to have_content(terraform_state.name)
end
end
end
end
context 'when user is a terraform developer' do
let_it_be(:developer) { create(:user) }
before do
project.add_developer(developer)
gitlab_sign_in(developer)
visit project_terraform_index_path(project)
end
context 'when user visits the index page' do
it 'displays a table without an action dropdown', :aggregate_failures do
expect(page).to have_selector(
'[data-testid="terraform-states-table"] tbody tr',
'[data-testid="terraform-states-table-name"]',
count: project.terraform_states.size
)
end
it 'displays terraform information' do
expect(page).to have_content(terraform_state.name)
expect(page).not_to have_selector('[data-testid*="terraform-state-actions"]')
end
end
end
......
import { GlDropdown } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import StateActions from '~/terraform/components/states_table_actions.vue';
describe('StatesTableActions', () => {
let wrapper;
const defaultProps = {
state: {
id: 'gid/1',
name: 'state-1',
latestVersion: { downloadPath: '/path' },
},
};
const createComponent = (propsData = defaultProps) => {
wrapper = shallowMount(StateActions, {
propsData,
stubs: { GlDropdown },
});
return wrapper.vm.$nextTick();
};
const findDownloadBtn = () => wrapper.find('[data-testid="terraform-state-download"]');
afterEach(() => {
wrapper.destroy();
});
describe('when state has a latestVersion', () => {
beforeEach(() => {
return createComponent();
});
it('displays a download button', () => {
const downloadBtn = findDownloadBtn();
expect(downloadBtn.text()).toBe('Download JSON');
});
});
describe('when state does not have a latestVersion', () => {
beforeEach(() => {
return createComponent({
state: {
id: 'gid/1',
name: 'state-1',
latestVersion: null,
},
});
});
it('does not display a download button', () => {
expect(findDownloadBtn().exists()).toBe(false);
});
});
});
import { GlIcon, GlTooltip } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { useFakeDate } from 'helpers/fake_date';
import StateActions from '~/terraform/components/states_table_actions.vue';
import StatesTable from '~/terraform/components/states_table.vue';
describe('StatesTable', () => {
let wrapper;
useFakeDate([2020, 10, 15]);
const propsData = {
const defaultProps = {
states: [
{
name: 'state-1',
......@@ -52,9 +53,15 @@ describe('StatesTable', () => {
],
};
beforeEach(() => {
const createComponent = (propsData = defaultProps) => {
wrapper = mount(StatesTable, { propsData });
return wrapper.vm.$nextTick();
};
const findActions = () => wrapper.findAll(StateActions);
beforeEach(() => {
return createComponent();
});
afterEach(() => {
......@@ -99,4 +106,21 @@ describe('StatesTable', () => {
expect(state.text()).toMatchInterpolatedText(updateTime);
});
it('displays no actions dropdown', () => {
expect(findActions().length).toEqual(0);
});
describe('when user is a terraform administrator', () => {
beforeEach(() => {
return createComponent({
terraformAdmin: true,
...defaultProps,
});
});
it('displays an actions dropdown for each state', () => {
expect(findActions().length).toEqual(defaultProps.states.length);
});
});
});
......@@ -5,10 +5,11 @@ require 'spec_helper'
RSpec.describe Projects::TerraformHelper do
describe '#js_terraform_list_data' do
let_it_be(:project) { create(:project) }
let(:current_user) { project.creator }
subject { helper.js_terraform_list_data(project) }
subject { helper.js_terraform_list_data(current_user, project) }
it 'displays image path' do
it 'includes image path' do
image_path = ActionController::Base.helpers.image_path(
'illustrations/empty-state/empty-serverless-lg.svg'
)
......@@ -16,8 +17,28 @@ RSpec.describe Projects::TerraformHelper do
expect(subject[:empty_state_image]).to eq(image_path)
end
it 'displays project path' do
it 'includes project path' do
expect(subject[:project_path]).to eq(project.full_path)
end
it 'indicates the user is a terraform admin' do
expect(subject[:terraform_admin]).to eq(true)
end
context 'when current_user is not a terraform admin' do
let(:current_user) { create(:user) }
it 'indicates the user is not an admin' do
expect(subject[:terraform_admin]).to eq(false)
end
end
context 'when current_user is missing' do
let(:current_user) { nil }
it 'indicates the user is not an admin' do
expect(subject[:terraform_admin]).to be_nil
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