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,6 +6,9 @@ ...@@ -6,6 +6,9 @@
.card-body .card-body
= render "projects/triggers/form", btn_text: "Add trigger" = render "projects/triggers/form", btn_text: "Add trigger"
%hr %hr
- if Feature.enabled?(:ci_pipeline_triggers_settings_vue_ui, @project)
#js-ci-pipeline-triggers-list.triggers-list{ data: { triggers: @triggers_json } }
- else
- if @triggers.any? - if @triggers.any?
.table-responsive.triggers-list .table-responsive.triggers-list
%table.table %table.table
...@@ -21,7 +24,7 @@ ...@@ -21,7 +24,7 @@
%th %th
= render partial: 'projects/triggers/trigger', collection: @triggers, as: :trigger = render partial: 'projects/triggers/trigger', collection: @triggers, as: :trigger
- else - else
%p.settings-message.text-center.gl-mb-3 %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. 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,6 +19,7 @@ RSpec.describe 'Triggers', :js do ...@@ -19,6 +19,7 @@ RSpec.describe 'Triggers', :js do
visit project_settings_ci_cd_path(@project) visit project_settings_ci_cd_path(@project)
end end
shared_examples 'triggers page' do
describe 'create trigger workflow' do describe 'create trigger workflow' do
it 'prevents adding new trigger with no description' do it 'prevents adding new trigger with no description' do
fill_in 'trigger_description', with: '' fill_in 'trigger_description', with: ''
...@@ -32,12 +33,13 @@ RSpec.describe 'Triggers', :js do ...@@ -32,12 +33,13 @@ RSpec.describe 'Triggers', :js 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 describe 'edit trigger workflow' do
let(:new_trigger_title) { 'new trigger' } let(:new_trigger_title) { 'new trigger' }
...@@ -60,12 +62,13 @@ RSpec.describe 'Triggers', :js do ...@@ -60,12 +62,13 @@ RSpec.describe 'Triggers', :js do
fill_in 'trigger_description', with: new_trigger_title fill_in 'trigger_description', with: new_trigger_title
click_button 'Save trigger' click_button 'Save trigger'
# See if "trigger updated successfully" message displayed and description and owner are correct 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('.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')).to have_content new_trigger_title
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 'trigger "Revoke" workflow' do describe 'trigger "Revoke" workflow' do
before do before do
...@@ -75,17 +78,19 @@ RSpec.describe 'Triggers', :js do ...@@ -75,17 +78,19 @@ RSpec.describe 'Triggers', :js do
it 'button "Revoke" has correct alert' do 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?' 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 expect(page.find('[data-testid="trigger_revoke_button"]')['data-confirm']).to eq expected_alert
end end
it 'revoke trigger' do it 'revoke trigger' do
# See if "Revoke" on trigger works post trigger creation # See if "Revoke" on trigger works post trigger creation
page.accept_confirm do page.accept_confirm do
find('a.btn-trigger-revoke').send_keys(:return) find('[data-testid="trigger_revoke_button"]').send_keys(:return)
end end
aggregate_failures 'trigger is removed' do
expect(page.find('.flash-notice')).to have_content 'Trigger removed' expect(page.find('.flash-notice')).to have_content 'Trigger removed'
expect(page).to have_selector('p.settings-message.text-center.gl-mb-3') expect(page).to have_css('[data-testid="no_triggers_content"]')
end
end end
end end
...@@ -98,35 +103,48 @@ RSpec.describe 'Triggers', :js do ...@@ -98,35 +103,48 @@ RSpec.describe 'Triggers', :js do
create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title) create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project) 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')).to have_content 'invalid'
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end 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)
# See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button 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')).to have_content(@project.triggers.first.token[0..3])
expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard') expect(page.find('.triggers-list')).not_to have_selector('[data-testid="clipboard-btn"]')
# 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 .trigger-owner')).not_to have_content user.name
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]') expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end end
end
it 'show "Edit" and full token for owned trigger' do it 'show "Edit" and full token for owned trigger' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title) create(:ci_trigger, owner: user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project) visit project_settings_ci_cd_path(@project)
# See if trigger shows full token and has copy-to-clipboard button 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_content @project.triggers.first.token
expect(page.find('.triggers-list')).to have_selector('button.btn-clipboard') expect(page.find('.triggers-list')).to have_selector('[data-testid="clipboard-btn"]')
# 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 .trigger-owner')).to have_content user.name
expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]') expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
end end
end end
end
end
context 'when ci_pipeline_triggers_settings_vue_ui is enabled' do
it_behaves_like 'triggers page'
end
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 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