Commit 449dbc43 authored by Kerri Miller's avatar Kerri Miller

Merge branch '42742-move-triggers-table-to-vue' into 'master'

Migrate triggers setting table to Vue

See merge request gitlab-org/gitlab!41864
parents 69caaa62 8633c996
<script>
import { GlTable, GlButton, GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
export default {
components: {
GlTable,
GlButton,
GlBadge,
ClipboardButton,
TooltipOnTruncate,
UserAvatarLink,
TimeAgoTooltip,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
triggers: {
type: Array,
required: false,
default: () => [],
},
},
fields: [
{
key: 'token',
label: s__('Pipelines|Token'),
},
{
key: 'description',
label: s__('Pipelines|Description'),
},
{
key: 'owner',
label: s__('Pipelines|Owner'),
},
{
key: 'lastUsed',
label: s__('Pipelines|Last Used'),
},
{
key: 'actions',
label: '',
tdClass: 'gl-text-right gl-white-space-nowrap',
},
],
};
</script>
<template>
<div>
<gl-table
v-if="triggers.length"
:fields="$options.fields"
:items="triggers"
class="triggers-list"
responsive
>
<template #cell(token)="{item}">
{{ item.token }}
<clipboard-button
v-if="item.hasTokenExposed"
:text="item.token"
data-testid="clipboard-btn"
data-qa-selector="clipboard_button"
:title="s__('Pipelines|Copy trigger token')"
css-class="gl-border-none gl-py-0 gl-px-2"
/>
<div class="label-container">
<gl-badge v-if="!item.canAccessProject" variant="danger">
<span
v-gl-tooltip.viewport
boundary="viewport"
:title="s__('Pipelines|Trigger user has insufficient permissions to project')"
>{{ s__('Pipelines|invalid') }}</span
>
</gl-badge>
</div>
</template>
<template #cell(description)="{item}">
<tooltip-on-truncate
:title="item.description"
truncate-target="child"
placement="top"
class="trigger-description gl-display-flex"
>
<div class="gl-flex-fill-1 gl-text-truncate">{{ item.description }}</div>
</tooltip-on-truncate>
</template>
<template #cell(owner)="{item}">
<span class="trigger-owner sr-only">{{ item.owner.name }}</span>
<user-avatar-link
v-if="item.owner"
:link-href="item.owner.path"
:img-src="item.owner.avatarUrl"
:tooltip-text="item.owner.name"
:img-alt="item.owner.name"
/>
</template>
<template #cell(lastUsed)="{item}">
<time-ago-tooltip v-if="item.lastUsed" :time="item.lastUsed" />
<span v-else>{{ __('Never') }}</span>
</template>
<template #cell(actions)="{item}">
<gl-button
:title="s__('Pipelines|Edit')"
icon="pencil"
data-testid="edit-btn"
:href="item.editProjectTriggerPath"
/>
<gl-button
:title="s__('Pipelines|Revoke')"
icon="remove"
variant="warning"
:data-confirm="
s__(
'Pipelines|By revoking a trigger you will break any processes making use of it. Are you sure?',
)
"
data-method="delete"
rel="nofollow"
class="gl-ml-3"
data-testid="trigger_revoke_button"
data-qa-selector="trigger_revoke_button"
:href="item.projectTriggerPath"
/>
</template>
</gl-table>
<div
v-else
data-testid="no_triggers_content"
data-qa-selector="no_triggers_content"
class="settings-message gl-text-center gl-mb-3"
>
{{ s__('Pipelines|No triggers have been created yet. Add one using the form above.') }}
</div>
</div>
</template>
import Vue from 'vue';
import TriggersList from './components/triggers_list.vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
const parseJsonArray = triggers => {
try {
return convertObjectPropsToCamelCase(JSON.parse(triggers), { deep: true });
} catch {
return [];
}
};
export default (containerId = 'js-ci-pipeline-triggers-list') => {
const containerEl = document.getElementById(containerId);
// Note: Remove this check when FF `ci_pipeline_triggers_settings_vue_ui` is removed.
if (!containerEl) {
return null;
}
const triggers = parseJsonArray(containerEl.dataset.triggers);
return new Vue({
el: containerEl,
components: {
TriggersList,
},
render(h) {
return h(TriggersList, {
props: {
triggers,
},
});
},
});
};
...@@ -4,6 +4,7 @@ import AjaxVariableList from '~/ci_variable_list/ajax_variable_list'; ...@@ -4,6 +4,7 @@ import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
import registrySettingsApp from '~/registry/settings/registry_settings_bundle'; import registrySettingsApp from '~/registry/settings/registry_settings_bundle';
import initVariableList from '~/ci_variable_list'; import initVariableList from '~/ci_variable_list';
import initDeployFreeze from '~/deploy_freeze'; import initDeployFreeze from '~/deploy_freeze';
import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels // Initialize expandable settings panels
...@@ -42,4 +43,6 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -42,4 +43,6 @@ document.addEventListener('DOMContentLoaded', () => {
registrySettingsApp(); registrySettingsApp();
initDeployFreeze(); initDeployFreeze();
initSettingsPipelinesTriggers();
}); });
...@@ -5,6 +5,10 @@ ...@@ -5,6 +5,10 @@
} }
} }
.trigger-description {
max-width: 100px;
}
.trigger-actions { .trigger-actions {
white-space: nowrap; white-space: nowrap;
......
...@@ -12,6 +12,11 @@ module Projects ...@@ -12,6 +12,11 @@ module Projects
end end
def show def show
if Feature.enabled?(:ci_pipeline_triggers_settings_vue_ui, @project)
@triggers_json = ::Ci::TriggerSerializer.new.represent(
@project.triggers, current_user: current_user, project: @project
).to_json
end
end end
def update def update
...@@ -116,6 +121,7 @@ module Projects ...@@ -116,6 +121,7 @@ module Projects
def define_triggers_variables def define_triggers_variables
@triggers = @project.triggers @triggers = @project.triggers
.present(current_user: current_user) .present(current_user: current_user)
@trigger = ::Ci::Trigger.new @trigger = ::Ci::Trigger.new
.present(current_user: current_user) .present(current_user: current_user)
end end
......
# frozen_string_literal: true
module Ci
class TriggerEntity < Grape::Entity
include Gitlab::Routing
include Gitlab::Allowable
expose :description
expose :owner, using: UserEntity
expose :last_used
expose :token do |trigger|
can_admin_trigger?(trigger) ? trigger.token : trigger.short_token
end
expose :has_token_exposed do |trigger|
can_admin_trigger?(trigger)
end
expose :can_access_project do |trigger|
trigger.can_access_project?
end
expose :project_trigger_path, if: -> (trigger) { can_manage_trigger?(trigger) } do |trigger|
project_trigger_path(options[:project], trigger)
end
expose :edit_project_trigger_path, if: -> (trigger) { can_admin_trigger?(trigger) } do |trigger|
edit_project_trigger_path(options[:project], trigger)
end
private
def can_manage_trigger?(trigger)
can?(options[:current_user], :manage_trigger, trigger)
end
def can_admin_trigger?(trigger)
can?(options[:current_user], :admin_trigger, trigger)
end
end
end
# frozen_string_literal: true
module Ci
class TriggerSerializer < BaseSerializer
entity ::Ci::TriggerEntity
end
end
...@@ -6,23 +6,26 @@ ...@@ -6,23 +6,26 @@
.card-body .card-body
= render "projects/triggers/form", btn_text: "Add trigger" = render "projects/triggers/form", btn_text: "Add trigger"
%hr %hr
- if @triggers.any? - if Feature.enabled?(:ci_pipeline_triggers_settings_vue_ui, @project)
.table-responsive.triggers-list #js-ci-pipeline-triggers-list.triggers-list{ data: { triggers: @triggers_json } }
%table.table
%thead
%th
%strong Token
%th
%strong Description
%th
%strong Owner
%th
%strong Last used
%th
= render partial: 'projects/triggers/trigger', collection: @triggers, as: :trigger
- else - else
%p.settings-message.text-center.gl-mb-3 - if @triggers.any?
No triggers have been created yet. Add one using the form above. .table-responsive.triggers-list
%table.table
%thead
%th
%strong Token
%th
%strong Description
%th
%strong Owner
%th
%strong Last used
%th
= render partial: 'projects/triggers/trigger', collection: @triggers, as: :trigger
- else
%p.settings-message.text-center.gl-mb-3{ data: { testid: 'no_triggers_content' } }
No triggers have been created yet. Add one using the form above.
.card-footer .card-footer
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%td %td
- if trigger.has_token_exposed? - if trigger.has_token_exposed?
%span= trigger.token %span= trigger.token
= clipboard_button(text: trigger.token, title: _("Copy trigger token")) = clipboard_button(text: trigger.token, title: _("Copy trigger token"), testid: 'clipboard-btn')
- else - else
%span= trigger.short_token %span= trigger.short_token
...@@ -33,5 +33,5 @@ ...@@ -33,5 +33,5 @@
= link_to edit_project_trigger_path(@project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do = link_to edit_project_trigger_path(@project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
= sprite_icon('pencil') = sprite_icon('pencil')
- if can?(current_user, :manage_trigger, trigger) - if can?(current_user, :manage_trigger, trigger)
= link_to project_trigger_path(@project, trigger), data: { confirm: revoke_trigger_confirmation }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do = link_to project_trigger_path(@project, trigger), data: { confirm: revoke_trigger_confirmation, testid: 'trigger_revoke_button' }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do
= sprite_icon('remove') = sprite_icon('remove')
---
name: ci_pipeline_triggers_settings_vue_ui
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41864
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/247486
group: group::continuous integration
type: development
default_enabled: false
...@@ -18665,6 +18665,9 @@ msgstr "" ...@@ -18665,6 +18665,9 @@ msgstr ""
msgid "Pipelines|Build with confidence" msgid "Pipelines|Build with confidence"
msgstr "" msgstr ""
msgid "Pipelines|By revoking a trigger you will break any processes making use of it. Are you sure?"
msgstr ""
msgid "Pipelines|CI Lint" msgid "Pipelines|CI Lint"
msgstr "" msgstr ""
...@@ -18677,6 +18680,15 @@ msgstr "" ...@@ -18677,6 +18680,15 @@ msgstr ""
msgid "Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment." msgid "Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment."
msgstr "" msgstr ""
msgid "Pipelines|Copy trigger token"
msgstr ""
msgid "Pipelines|Description"
msgstr ""
msgid "Pipelines|Edit"
msgstr ""
msgid "Pipelines|Get started with Pipelines" msgid "Pipelines|Get started with Pipelines"
msgstr "" msgstr ""
...@@ -18692,15 +18704,27 @@ msgstr "" ...@@ -18692,15 +18704,27 @@ msgstr ""
msgid "Pipelines|It is recommended the code is reviewed thoroughly before running this pipeline with the parent project's CI resource." msgid "Pipelines|It is recommended the code is reviewed thoroughly before running this pipeline with the parent project's CI resource."
msgstr "" msgstr ""
msgid "Pipelines|Last Used"
msgstr ""
msgid "Pipelines|Loading Pipelines" msgid "Pipelines|Loading Pipelines"
msgstr "" msgstr ""
msgid "Pipelines|More Information" msgid "Pipelines|More Information"
msgstr "" msgstr ""
msgid "Pipelines|No triggers have been created yet. Add one using the form above."
msgstr ""
msgid "Pipelines|Owner"
msgstr ""
msgid "Pipelines|Project cache successfully reset." msgid "Pipelines|Project cache successfully reset."
msgstr "" msgstr ""
msgid "Pipelines|Revoke"
msgstr ""
msgid "Pipelines|Run Pipeline" msgid "Pipelines|Run Pipeline"
msgstr "" msgstr ""
...@@ -18725,6 +18749,15 @@ msgstr "" ...@@ -18725,6 +18749,15 @@ msgstr ""
msgid "Pipelines|This project is not currently set up to run pipelines." msgid "Pipelines|This project is not currently set up to run pipelines."
msgstr "" msgstr ""
msgid "Pipelines|Token"
msgstr ""
msgid "Pipelines|Trigger user has insufficient permissions to project"
msgstr ""
msgid "Pipelines|invalid"
msgstr ""
msgid "Pipelines|parent" msgid "Pipelines|parent"
msgstr "" msgstr ""
......
...@@ -19,114 +19,132 @@ RSpec.describe 'Triggers', :js do ...@@ -19,114 +19,132 @@ RSpec.describe 'Triggers', :js do
visit project_settings_ci_cd_path(@project) visit project_settings_ci_cd_path(@project)
end end
describe 'create trigger workflow' do shared_examples 'triggers page' do
it 'prevents adding new trigger with no description' do describe 'create trigger workflow' do
fill_in 'trigger_description', with: '' it 'prevents adding new trigger with no description' do
click_button 'Add trigger' fill_in 'trigger_description', with: ''
click_button 'Add trigger'
# See if input has error due to empty value
expect(page.find('form.gl-show-field-errors .gl-field-error')).to be_visible # See if input has error due to empty value
end expect(page.find('form.gl-show-field-errors .gl-field-error')).to be_visible
end
it 'adds new trigger with description' do it 'adds new trigger with description' do
fill_in 'trigger_description', with: 'trigger desc' fill_in 'trigger_description', with: 'trigger desc'
click_button 'Add trigger' click_button 'Add trigger'
# See if "trigger creation successful" message displayed and description and owner are correct aggregate_failures 'display creation notice and trigger is created' do
expect(page.find('.flash-notice')).to have_content 'Trigger was created successfully.' expect(page.find('.flash-notice')).to have_content 'Trigger was created successfully.'
expect(page.find('.triggers-list')).to have_content 'trigger desc' expect(page.find('.triggers-list')).to have_content 'trigger desc'
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
end
end
end end
end
describe 'edit trigger workflow' do
let(:new_trigger_title) { 'new trigger' }
it 'click on edit trigger opens edit trigger page' do describe 'edit trigger workflow' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title) let(:new_trigger_title) { 'new trigger' }
visit project_settings_ci_cd_path(@project)
# See if edit page has correct descrption it 'click on edit trigger opens edit trigger page' do
find('a[title="Edit"]').send_keys(:return) create(:ci_trigger, owner: user, project: @project, description: trigger_title)
expect(page.find('#trigger_description').value).to have_content 'trigger desc' visit project_settings_ci_cd_path(@project)
end
it 'edit trigger and save' do # See if edit page has correct descrption
create(:ci_trigger, owner: user, project: @project, description: trigger_title) find('a[title="Edit"]').send_keys(:return)
visit project_settings_ci_cd_path(@project) expect(page.find('#trigger_description').value).to have_content 'trigger desc'
end
# See if edit page opens, then fill in new description and save it 'edit trigger and save' do
find('a[title="Edit"]').send_keys(:return) create(:ci_trigger, owner: user, project: @project, description: trigger_title)
fill_in 'trigger_description', with: new_trigger_title visit project_settings_ci_cd_path(@project)
click_button 'Save trigger'
# See if "trigger updated successfully" message displayed and description and owner are correct # See if edit page opens, then fill in new description and save
expect(page.find('.flash-notice')).to have_content 'Trigger was successfully updated.' find('a[title="Edit"]').send_keys(:return)
expect(page.find('.triggers-list')).to have_content new_trigger_title fill_in 'trigger_description', with: new_trigger_title
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name click_button 'Save trigger'
end
end
describe 'trigger "Revoke" workflow' do aggregate_failures 'display update notice and trigger is updated' do
before do expect(page.find('.flash-notice')).to have_content 'Trigger was successfully updated.'
create(:ci_trigger, owner: user2, project: @project, description: trigger_title) expect(page.find('.triggers-list')).to have_content new_trigger_title
visit project_settings_ci_cd_path(@project) expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
end
end
end end
it 'button "Revoke" has correct alert' do describe 'trigger "Revoke" workflow' do
expected_alert = 'By revoking a trigger you will break any processes making use of it. Are you sure?' before do
expect(page.find('a.btn-trigger-revoke')['data-confirm']).to eq expected_alert create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
end visit project_settings_ci_cd_path(@project)
end
it 'revoke trigger' do it 'button "Revoke" has correct alert' do
# See if "Revoke" on trigger works post trigger creation expected_alert = 'By revoking a trigger you will break any processes making use of it. Are you sure?'
page.accept_confirm do expect(page.find('[data-testid="trigger_revoke_button"]')['data-confirm']).to eq expected_alert
find('a.btn-trigger-revoke').send_keys(:return)
end end
expect(page.find('.flash-notice')).to have_content 'Trigger removed' it 'revoke trigger' do
expect(page).to have_selector('p.settings-message.text-center.gl-mb-3') # See if "Revoke" on trigger works post trigger creation
end page.accept_confirm do
end find('[data-testid="trigger_revoke_button"]').send_keys(:return)
end
describe 'show triggers workflow' do aggregate_failures 'trigger is removed' do
it 'contains trigger description placeholder' do expect(page.find('.flash-notice')).to have_content 'Trigger removed'
expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description' expect(page).to have_css('[data-testid="no_triggers_content"]')
end
end
end end
it 'show "invalid" badge for trigger with owner having insufficient permissions' do describe 'show triggers workflow' do
create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title) it 'contains trigger description placeholder' do
visit project_settings_ci_cd_path(@project) expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description'
end
expect(page.find('.triggers-list')).to have_content 'invalid' it 'show "invalid" badge for trigger with owner having insufficient permissions' do
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
end visit project_settings_ci_cd_path(@project)
aggregate_failures 'has invalid badge and no edit link' do
expect(page.find('.triggers-list')).to have_content 'invalid'
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end
end
it 'do not show "Edit" or full token for not owned trigger' do it 'do not show "Edit" or full token for not owned trigger' do
# Create trigger with user different from current_user # Create trigger with user different from current_user
create(:ci_trigger, owner: user2, project: @project, description: trigger_title) create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project) visit project_settings_ci_cd_path(@project)
aggregate_failures 'shows truncated token, no clipboard button and no edit link' do
expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
expect(page.find('.triggers-list')).not_to have_selector('[data-testid="clipboard-btn"]')
expect(page.find('.triggers-list .trigger-owner')).not_to have_content user.name
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end
end
# See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button it 'show "Edit" and full token for owned trigger' do
expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3]) create(:ci_trigger, owner: user, project: @project, description: trigger_title)
expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard') visit project_settings_ci_cd_path(@project)
# See if trigger owner name doesn't match with current_user and trigger is non-editable aggregate_failures 'shows full token, clipboard button and edit link' do
expect(page.find('.triggers-list .trigger-owner')).not_to have_content user.name expect(page.find('.triggers-list')).to have_content @project.triggers.first.token
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') expect(page.find('.triggers-list')).to have_selector('[data-testid="clipboard-btn"]')
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
end
end
end end
end
it 'show "Edit" and full token for owned trigger' do context 'when ci_pipeline_triggers_settings_vue_ui is enabled' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title) it_behaves_like 'triggers page'
visit project_settings_ci_cd_path(@project) end
# See if trigger shows full token and has copy-to-clipboard button
expect(page.find('.triggers-list')).to have_content @project.triggers.first.token
expect(page.find('.triggers-list')).to have_selector('button.btn-clipboard')
# See if trigger owner name matches with current_user and is editable context 'when ci_pipeline_triggers_settings_vue_ui is disabled' do
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name before do
expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') stub_feature_flags(ci_pipeline_triggers_settings_vue_ui: false)
end end
it_behaves_like 'triggers page'
end end
end end
{
"type": "object",
"required": [
"description",
"owner",
"last_used",
"has_token_exposed",
"token",
"can_access_project"
],
"properties": {
"description": {
"type": ["string", "null"]
},
"owner": {
"type": "object",
"$ref": "user.json"
},
"last_used": {
"type": ["datetime", "null"]
},
"token": {
"type": "string"
},
"has_token_exposed": {
"type": "boolean"
},
"can_access_project": {
"type": "boolean"
},
"edit_project_trigger_path": {
"type": "string"
},
"project_trigger_path": {
"type": "string"
}
},
"additionalProperties": false
}
import { mount } from '@vue/test-utils';
import { GlTable, GlBadge } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import TriggersList from '~/ci_settings_pipeline_triggers/components/triggers_list.vue';
import { triggers } from '../mock_data';
describe('TriggersList', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = mount(TriggersList, {
propsData: { triggers, ...props },
});
};
const findTable = () => wrapper.find(GlTable);
const findHeaderAt = i => wrapper.findAll('thead th').at(i);
const findRows = () => wrapper.findAll('tbody tr');
const findRowAt = i => findRows().at(i);
const findCell = (i, col) =>
findRowAt(i)
.findAll('td')
.at(col);
const findClipboardBtn = i => findCell(i, 0).find(ClipboardButton);
const findInvalidBadge = i => findCell(i, 0).find(GlBadge);
const findEditBtn = i => findRowAt(i).find('[data-testid="edit-btn"]');
const findRevokeBtn = i => findRowAt(i).find('[data-testid="trigger_revoke_button"]');
beforeEach(() => {
createComponent();
return wrapper.vm.$nextTick();
});
it('displays a table with expected headers', () => {
const headers = ['Token', 'Description', 'Owner', 'Last Used', ''];
headers.forEach((header, i) => {
expect(findHeaderAt(i).text()).toBe(header);
});
});
it('displays a table with rows', () => {
expect(findRows()).toHaveLength(triggers.length);
const [trigger] = triggers;
expect(findCell(0, 0).text()).toBe(trigger.token);
expect(findCell(0, 1).text()).toBe(trigger.description);
expect(findCell(0, 2).text()).toContain(trigger.owner.name);
});
it('displays a "copy to cliboard" button for exposed tokens', () => {
expect(findClipboardBtn(0).exists()).toBe(true);
expect(findClipboardBtn(0).props('text')).toBe(triggers[0].token);
expect(findClipboardBtn(1).exists()).toBe(false);
});
it('displays an "invalid" label for tokens without access', () => {
expect(findInvalidBadge(0).exists()).toBe(false);
expect(findInvalidBadge(1).exists()).toBe(true);
});
it('displays a time ago label when last used', () => {
expect(findCell(0, 3).text()).toBe('Never');
expect(
findCell(1, 3)
.find(TimeAgoTooltip)
.props('time'),
).toBe(triggers[1].lastUsed);
});
it('displays actions in a rows', () => {
const [data] = triggers;
expect(findEditBtn(0).attributes('href')).toBe(data.editProjectTriggerPath);
expect(findRevokeBtn(0).attributes('href')).toBe(data.projectTriggerPath);
expect(findRevokeBtn(0).attributes('data-method')).toBe('delete');
expect(findRevokeBtn(0).attributes('data-confirm')).toBeTruthy();
});
describe('when there are no triggers set', () => {
beforeEach(() => {
createComponent({ triggers: [] });
});
it('does not display a table', () => {
expect(findTable().exists()).toBe(false);
});
it('displays a message', () => {
expect(wrapper.text()).toBe(
'No triggers have been created yet. Add one using the form above.',
);
});
});
});
export const triggers = [
{
hasTokenExposed: true,
token: '0000',
description: 'My trigger',
owner: {
name: 'My User',
username: 'user1',
path: '/user1',
},
lastUsed: null,
canAccessProject: true,
editProjectTriggerPath: '/triggers/1/edit',
projectTriggerPath: '/trigger/1',
},
{
hasTokenExposed: false,
token: '1111',
description: "Anothe user's trigger",
owner: {
name: 'Someone else',
username: 'user2',
path: '/user2',
},
lastUsed: '2020-09-10T08:26:47.410Z',
canAccessProject: false,
editProjectTriggerPath: '/triggers/1/edit',
projectTriggerPath: '/trigger/1',
},
];
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::TriggerEntity do
let(:project) { create(:project) }
let(:trigger) { create(:ci_trigger, project: project, token: '237f3604900a4cd71ed06ef13e57b96d') }
let(:user) { create(:user) }
let(:entity) { described_class.new(trigger, current_user: user, project: project) }
describe '#as_json' do
let(:as_json) { entity.as_json }
let(:project_trigger_path) { "/#{project.full_path}/-/triggers/#{trigger.id}" }
it 'contains required fields' do
expect(as_json).to include(
:description, :owner, :last_used, :token, :has_token_exposed, :can_access_project
)
end
it 'contains user fields' do
expect(as_json[:owner].to_json).to match_schema('entities/user')
end
context 'when current user can manage triggers' do
before do
project.add_maintainer(user)
end
it 'returns short_token as token' do
expect(as_json[:token]).to eq(trigger.short_token)
end
it 'contains project_trigger_path' do
expect(as_json[:project_trigger_path]).to eq(project_trigger_path)
end
it 'does not contain edit_project_trigger_path' do
expect(as_json).not_to include(:edit_project_trigger_path)
end
it 'returns has_token_exposed' do
expect(as_json[:has_token_exposed]).to eq(false)
end
end
context 'when current user is the owner of the trigger' do
before do
project.add_maintainer(user)
trigger.update!(owner: user)
end
it 'returns token as token' do
expect(as_json[:token]).to eq(trigger.token)
end
it 'contains project_trigger_path' do
expect(as_json[:project_trigger_path]).to eq(project_trigger_path)
end
it 'contains edit_project_trigger_path' do
expect(as_json[:edit_project_trigger_path]).to eq("#{project_trigger_path}/edit")
end
it 'returns has_token_exposed' do
expect(as_json[:has_token_exposed]).to eq(true)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::TriggerSerializer do
describe '#represent' do
let(:represent) { described_class.new.represent(trigger) }
let(:trigger) { build(:ci_trigger) }
it 'matches schema' do
expect(represent.to_json).to match_schema('entities/trigger')
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