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,16 +4,21 @@ 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)
gitlab_sign_in(admin)
end
context 'when user does not have any terraform states and visits index page' do
context 'when user does not have any terraform states and visits the index page' do
let(:empty_project) { create(:project) }
before do
visit project_terraform_index_path(project)
empty_project.add_maintainer(admin)
visit project_terraform_index_path(empty_project)
end
it 'sees an empty state' do
......@@ -22,8 +27,6 @@ RSpec.describe 'Terraform', :js do
end
context 'when user has a terraform state' do
let_it_be(:terraform_state) { create(:terraform_state, :locked, project: project) }
context 'when user visits the index page' do
before do
visit project_terraform_index_path(project)
......@@ -35,7 +38,14 @@ RSpec.describe 'Terraform', :js do
it 'displays a table with terraform states' 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 actions dropdown' do
expect(page).to have_selector(
'[data-testid*="terraform-state-actions"]',
count: project.terraform_states.size
)
end
......@@ -45,4 +55,26 @@ RSpec.describe 'Terraform', :js do
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-name"]',
count: project.terraform_states.size
)
expect(page).not_to have_selector('[data-testid*="terraform-state-actions"]')
end
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