Commit 8633c996 authored by Miguel Rincon's avatar Miguel Rincon Committed by Kerri Miller

Migrate triggers settings table to Vue

So we can add more functionality to the triggers table we
would like to move the table from HAML to Vue.

As a temporary measure, this change loads the data from a JSON
string that gets added to the HTML of the page, instead of loading
the data via AJAX.
parent 8bf5e565
<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';
import registrySettingsApp from '~/registry/settings/registry_settings_bundle';
import initVariableList from '~/ci_variable_list';
import initDeployFreeze from '~/deploy_freeze';
import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
......@@ -42,4 +43,6 @@ document.addEventListener('DOMContentLoaded', () => {
registrySettingsApp();
initDeployFreeze();
initSettingsPipelinesTriggers();
});
......@@ -5,6 +5,10 @@
}
}
.trigger-description {
max-width: 100px;
}
.trigger-actions {
white-space: nowrap;
......
......@@ -12,6 +12,11 @@ module Projects
end
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
def update
......@@ -116,6 +121,7 @@ module Projects
def define_triggers_variables
@triggers = @project.triggers
.present(current_user: current_user)
@trigger = ::Ci::Trigger.new
.present(current_user: current_user)
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 @@
.card-body
= render "projects/triggers/form", btn_text: "Add trigger"
%hr
- if @triggers.any?
.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
- if Feature.enabled?(:ci_pipeline_triggers_settings_vue_ui, @project)
#js-ci-pipeline-triggers-list.triggers-list{ data: { triggers: @triggers_json } }
- else
%p.settings-message.text-center.gl-mb-3
No triggers have been created yet. Add one using the form above.
- if @triggers.any?
.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
......
......@@ -2,7 +2,7 @@
%td
- if trigger.has_token_exposed?
%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
%span= trigger.short_token
......@@ -33,5 +33,5 @@
= link_to edit_project_trigger_path(@project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
= sprite_icon('pencil')
- 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')
---
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
......@@ -18627,6 +18627,9 @@ msgstr ""
msgid "Pipelines|Build with confidence"
msgstr ""
msgid "Pipelines|By revoking a trigger you will break any processes making use of it. Are you sure?"
msgstr ""
msgid "Pipelines|CI Lint"
msgstr ""
......@@ -18639,6 +18642,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."
msgstr ""
msgid "Pipelines|Copy trigger token"
msgstr ""
msgid "Pipelines|Description"
msgstr ""
msgid "Pipelines|Edit"
msgstr ""
msgid "Pipelines|Get started with Pipelines"
msgstr ""
......@@ -18654,15 +18666,27 @@ msgstr ""
msgid "Pipelines|It is recommended the code is reviewed thoroughly before running this pipeline with the parent project's CI resource."
msgstr ""
msgid "Pipelines|Last Used"
msgstr ""
msgid "Pipelines|Loading Pipelines"
msgstr ""
msgid "Pipelines|More Information"
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."
msgstr ""
msgid "Pipelines|Revoke"
msgstr ""
msgid "Pipelines|Run Pipeline"
msgstr ""
......@@ -18687,6 +18711,15 @@ msgstr ""
msgid "Pipelines|This project is not currently set up to run pipelines."
msgstr ""
msgid "Pipelines|Token"
msgstr ""
msgid "Pipelines|Trigger user has insufficient permissions to project"
msgstr ""
msgid "Pipelines|invalid"
msgstr ""
msgid "Pipelines|parent"
msgstr ""
......
......@@ -19,114 +19,132 @@ RSpec.describe 'Triggers', :js do
visit project_settings_ci_cd_path(@project)
end
describe 'create trigger workflow' do
it 'prevents adding new trigger with no description' do
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
end
shared_examples 'triggers page' do
describe 'create trigger workflow' do
it 'prevents adding new trigger with no description' do
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
end
it 'adds new trigger with description' do
fill_in 'trigger_description', with: 'trigger desc'
click_button 'Add trigger'
it 'adds new trigger with description' do
fill_in 'trigger_description', with: 'trigger desc'
click_button 'Add trigger'
# See if "trigger creation successful" message displayed and description and owner are correct
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 .trigger-owner')).to have_content user.name
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('.triggers-list')).to have_content 'trigger desc'
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
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
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
describe 'edit trigger workflow' do
let(:new_trigger_title) { 'new trigger' }
# See if edit page has correct descrption
find('a[title="Edit"]').send_keys(:return)
expect(page.find('#trigger_description').value).to have_content 'trigger desc'
end
it 'click on edit trigger opens edit trigger page' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
it 'edit trigger and save' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
# See if edit page has correct descrption
find('a[title="Edit"]').send_keys(:return)
expect(page.find('#trigger_description').value).to have_content 'trigger desc'
end
# See if edit page opens, then fill in new description and save
find('a[title="Edit"]').send_keys(:return)
fill_in 'trigger_description', with: new_trigger_title
click_button 'Save trigger'
it 'edit trigger and save' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
# See if "trigger updated successfully" message displayed and description and owner are correct
expect(page.find('.flash-notice')).to have_content 'Trigger was successfully updated.'
expect(page.find('.triggers-list')).to have_content new_trigger_title
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
end
end
# See if edit page opens, then fill in new description and save
find('a[title="Edit"]').send_keys(:return)
fill_in 'trigger_description', with: new_trigger_title
click_button 'Save trigger'
describe 'trigger "Revoke" workflow' do
before do
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
aggregate_failures 'display update notice and trigger is updated' do
expect(page.find('.flash-notice')).to have_content 'Trigger was successfully updated.'
expect(page.find('.triggers-list')).to have_content new_trigger_title
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
end
end
end
it 'button "Revoke" has correct alert' do
expected_alert = 'By revoking a trigger you will break any processes making use of it. Are you sure?'
expect(page.find('a.btn-trigger-revoke')['data-confirm']).to eq expected_alert
end
describe 'trigger "Revoke" workflow' do
before do
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
end
it 'revoke trigger' do
# See if "Revoke" on trigger works post trigger creation
page.accept_confirm do
find('a.btn-trigger-revoke').send_keys(:return)
it 'button "Revoke" has correct alert' do
expected_alert = 'By revoking a trigger you will break any processes making use of it. Are you sure?'
expect(page.find('[data-testid="trigger_revoke_button"]')['data-confirm']).to eq expected_alert
end
expect(page.find('.flash-notice')).to have_content 'Trigger removed'
expect(page).to have_selector('p.settings-message.text-center.gl-mb-3')
end
end
it 'revoke trigger' do
# See if "Revoke" on trigger works post trigger creation
page.accept_confirm do
find('[data-testid="trigger_revoke_button"]').send_keys(:return)
end
describe 'show triggers workflow' do
it 'contains trigger description placeholder' do
expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description'
aggregate_failures 'trigger is removed' do
expect(page.find('.flash-notice')).to have_content 'Trigger removed'
expect(page).to have_css('[data-testid="no_triggers_content"]')
end
end
end
it 'show "invalid" badge for trigger with owner having insufficient permissions' do
create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
describe 'show triggers workflow' do
it 'contains trigger description placeholder' do
expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description'
end
expect(page.find('.triggers-list')).to have_content 'invalid'
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end
it 'show "invalid" badge for trigger with owner having insufficient permissions' do
create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
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
# Create trigger with user different from current_user
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
it 'do not show "Edit" or full token for not owned trigger' do
# Create trigger with user different from current_user
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
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
expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard')
it 'show "Edit" and full token for owned trigger' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
# See if trigger owner name doesn't match with current_user and trigger is non-editable
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"]')
aggregate_failures 'shows full token, clipboard button and edit link' do
expect(page.find('.triggers-list')).to have_content @project.triggers.first.token
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
it 'show "Edit" and full token for owned trigger' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
# 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')
context 'when ci_pipeline_triggers_settings_vue_ui is enabled' do
it_behaves_like 'triggers page'
end
# See if trigger owner name matches with current_user and is editable
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
context 'when ci_pipeline_triggers_settings_vue_ui is disabled' do
before do
stub_feature_flags(ci_pipeline_triggers_settings_vue_ui: false)
end
it_behaves_like 'triggers page'
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