Skip to content

Commit

Permalink
projectsettings: add ability to set membersCanPerformRunAction
Browse files Browse the repository at this point in the history
add an ui checkbox to let the project owner enable the project option to let members perform run actions as implemented in agola-io/agola#446
  • Loading branch information
raynasorint committed Dec 27, 2023
1 parent 6fd63d8 commit 532ed65
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 6 deletions.
10 changes: 9 additions & 1 deletion src/app/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export interface API {
remotesourcename: string,
remoterepopath: string,
passvarstoforkedpr: boolean,
membersCanPerformRunActions: boolean,
signal?: AbortSignal
): Promise<ProjectResponse>;

Expand All @@ -210,6 +211,7 @@ export interface API {
name: string,
visibility: string,
passvarstoforkedpr: boolean,
membersCanPerformRunActions: boolean,
signal?: AbortSignal
): Promise<ProjectResponse>;

Expand Down Expand Up @@ -912,7 +914,6 @@ export function newAPI(): API {
): Promise<ProjectGroupResponse> {
const apiURL = baseURL();
apiURL.pathname += '/projectgroups/' + encodeURIComponent(projectgroupref);

const init = {
method: 'PUT',
body: JSON.stringify({
Expand Down Expand Up @@ -969,6 +970,7 @@ export function newAPI(): API {
remotesourcename: string,
remoterepopath: string,
passvarstoforkedpr: boolean,
membersCanPerformRunActions: boolean,
signal?: AbortSignal
): Promise<ProjectResponse> {
const apiURL = baseURL();
Expand All @@ -983,6 +985,7 @@ export function newAPI(): API {
remote_source_name: remotesourcename,
repo_path: remoterepopath,
pass_vars_to_forked_pr: passvarstoforkedpr,
members_can_perform_run_actions: membersCanPerformRunActions,
}),
signal,
};
Expand All @@ -1000,6 +1003,7 @@ export function newAPI(): API {
name: string,
visibility: string,
passvarstoforkedpr: boolean,
membersCanPerformRunActions: boolean,
signal?: AbortSignal
): Promise<ProjectResponse> {
const apiURL = baseURL();
Expand All @@ -1011,6 +1015,7 @@ export function newAPI(): API {
name: name,
visibility: visibility,
pass_vars_to_forked_pr: passvarstoforkedpr,
members_can_perform_run_actions: membersCanPerformRunActions,
}),
signal,
};
Expand Down Expand Up @@ -1662,6 +1667,9 @@ export class ProjectResponse {

@jsonMember(Boolean, { name: 'pass_vars_to_forked_pr' })
passVarsToForkedPR = false;

@jsonMember(Boolean, { name: 'members_can_perform_run_actions' })
membersCanPerformRunActions = false;
}

export enum SecretType {
Expand Down
114 changes: 114 additions & 0 deletions src/components/createproject.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { flushPromises, mount, VueWrapper } from '@vue/test-utils';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { afterAll, afterEach, beforeAll } from 'vitest';
import { APIInjectionKey, newAPI } from '../app/api';
import { AppStateInjectionKey, newAppState } from '../app/appstate';
import createproject from './createproject.vue';
export const handlers = [
http.post('/api/v1alpha/projects', () => {
return HttpResponse.json({
name: 'proj01',
parent_ref: 'org/org01',
pass_vars_to_forked_pr: true,
remote_source_name: 'GiteaLocale',
repo_path: 'org/org01',
visibility: 'private',
members_can_perform_run_actions: true,
});
}),
http.get('/api/v1alpha/remotesources', () => {
return HttpResponse.json([
{
id: '8ee07254-baa9-4df8-87e3-faed60e05848',
name: 'GiteaLocale',
auth_type: 'oauth2',
registration_enabled: true,
login_enabled: true,
},
]);
}),
http.get(
'/api/v1alpha/user/remoterepos/8ee07254-baa9-4df8-87e3-faed60e05848',
() => {
return HttpResponse.json([{ id: '1', path: 'rivanova/testrayna' }]);
}
),
];

const server = setupServer(...handlers);

// Start server before all tests
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));

// Close server after all tests
afterAll(() => server.close());

// Reset handlers after each test `important for test isolation`
afterEach(() => server.resetHandlers());

let wrapper: VueWrapper;

beforeEach(async () => {
const api = newAPI();
const appState = newAppState();
wrapper = mount(createproject, {
global: {
provide: {
[APIInjectionKey as symbol]: api,
[AppStateInjectionKey as symbol]: appState,
},
},
props: {
ownertype: 'org',
ownername: 'org01',
projectgroupref: ['proj01'],
},
});
});

test('create a project', async () => {
await flushPromises();

const projectName = wrapper.find<HTMLInputElement>(
'[data-test="projectName"]'
);
const projectIsPrivate = wrapper.find<HTMLInputElement>(
'[data-test="projectIsPrivate"]'
);
const membersCanPerformRunActions = wrapper.find<HTMLInputElement>(
'[data-test="membersCanPerformRunActions"]'
);
const passVarsToForkedPR = wrapper.find<HTMLInputElement>(
'[data-test="passVarsToForkedPR"]'
);
const selectedRemoteSourceIndex = wrapper.find<HTMLInputElement>(
'[data-test="selectedRemoteSourceIndex"]'
);
const selectedRemoteSourceIndexButton = wrapper.find<HTMLInputElement>(
'[data-test="selectedRemoteSourceIndexButton"]'
);
await projectName.setValue('proj01');
await projectIsPrivate.setValue(true);
await membersCanPerformRunActions.setValue(true);
await selectedRemoteSourceIndex.setValue('0');
await passVarsToForkedPR.setValue(true);
await selectedRemoteSourceIndexButton.trigger('click');

await flushPromises();
const selectedRepo = wrapper.find<HTMLInputElement>(
'[data-test="selectedRepo-0"]'
);
await selectedRepo.setValue(true);
await selectedRepo.trigger('click');
const createProjectButton = wrapper.find('[data-test="createProjectButton"]');
expect(createProjectButton.attributes('disabled')).toBeFalsy();

createProjectButton.trigger('click');

await flushPromises();
expect(wrapper.router.push).toHaveBeenCalledTimes(1);
expect(wrapper.router.push).toHaveBeenCalledWith({
path: '/org/org01/projects/proj01/proj01.proj',
});
});
34 changes: 31 additions & 3 deletions src/components/createproject.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,35 @@
type="text"
placeholder="Project Name"
v-model="projectName"
data-test="projectName"
/>
<div class="mb-4">
<label>
<input type="checkbox" v-model="projectIsPrivate" />
<input
type="checkbox"
v-model="projectIsPrivate"
data-test="projectIsPrivate"
/>
Private
</label>
</div>
<div class="mb-4" v-if="ownertype === 'org'">
<label>
<input
type="checkbox"
v-model="membersCanPerformRunActions"
data-test="membersCanPerformRunActions"
/>
Members can perform run action (restart, stop and cancel a run)
</label>
</div>
<div class="mb-4">
<label>
<input type="checkbox" v-model="passVarsToForkedPR" />
<input
type="checkbox"
v-model="passVarsToForkedPR"
data-test="passVarsToForkedPR"
/>
Pass variables to run even if triggered by PR from forked repo
(DANGEROUS)
</label>
Expand All @@ -26,6 +45,7 @@
<select
class="block appearance-none w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline"
v-model="selectedRemoteSourceIndex"
data-test="selectedRemoteSourceIndex"
>
<option :value="undefined" disabled>Select the remote source</option>
<option
Expand All @@ -38,10 +58,12 @@
</select>
</div>
<button
type="button"
class="ml-3 btn btn-blue"
:class="{ spinner: fetchRemoteReposLoading }"
:disabled="selectedRemoteSourceIndex == null"
@click="refreshRemoteRepos()"
data-test="selectedRemoteSourceIndexButton"
>
Fetch remote repositories
</button>
Expand All @@ -54,10 +76,12 @@
v-on:reposelected="repoSelected($event)"
/>
<button
type="button"
class="btn btn-blue"
:class="{ spinner: createProjectLoading }"
:disabled="!createProjectButtonEnabled"
@click="createProject()"
data-test="createProjectButton"
>
Create Project
</button>
Expand Down Expand Up @@ -124,6 +148,7 @@ export default defineComponent({
const createProjectLoading = ref(false);
const fetchRemoteReposLoading = ref(false);
const membersCanPerformRunActions = ref(false);
onUnmounted(() => {
fetchAbort.abort();
Expand Down Expand Up @@ -172,7 +197,8 @@ export default defineComponent({
visibility,
remoteSource.name,
remoteRepoPath.value,
passVarsToForkedPR.value
passVarsToForkedPR.value,
membersCanPerformRunActions.value
);
let newProjectref = [projectName.value];
Expand Down Expand Up @@ -263,6 +289,7 @@ export default defineComponent({
projectName.value = '';
projectIsPrivate.value = false;
passVarsToForkedPR.value = false;
membersCanPerformRunActions.value = false;
update();
},
Expand All @@ -282,6 +309,7 @@ export default defineComponent({
createProjectButtonEnabled,
createProjectLoading,
fetchRemoteReposLoading,
membersCanPerformRunActions,
refreshRemoteRepos,
repoSelected,
Expand Down
43 changes: 43 additions & 0 deletions src/components/projectsettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const handlers = [
id: '1',
name: 'proj01',
visibility: 'public',
members_can_perform_run_actions: false,
});
}),

Expand All @@ -20,6 +21,7 @@ export const handlers = [
id: '1',
name: 'newproj01',
visibility: 'public',
members_can_perform_run_actions: true,
});
}),

Expand Down Expand Up @@ -78,11 +80,15 @@ test('update project name', async () => {
const projectName = wrapper.find<HTMLInputElement>(
'[data-test="projectNameInput"]'
);
const membersCanPerformRunActions = wrapper.find<HTMLInputElement>(
'[data-test="membersCanPerformRunActions"]'
);
const updateProjectButton = wrapper.find<HTMLInputElement>(
'[data-test="updateProjectButton"]'
);

projectName.setValue('newproj01');
membersCanPerformRunActions.setValue(true);
updateProjectButton.element.click();

await flushPromises();
Expand All @@ -92,3 +98,40 @@ test('update project name', async () => {
path: '/org/org01/projects/newproj01.proj/settings',
});
});

test('update org project membersCanPerformRunActions', async () => {
await flushPromises();

const membersCanPerformRunActions = wrapper.find<HTMLInputElement>(
'[data-test="membersCanPerformRunActions"]'
);
const updateProjectButton = wrapper.find<HTMLInputElement>(
'[data-test="updateProjectButton"]'
);
const initialValue = membersCanPerformRunActions.element.checked;

membersCanPerformRunActions.setValue(!initialValue);

updateProjectButton.element.click();

await flushPromises();

expect(membersCanPerformRunActions.element.checked).toBe(!initialValue);
});

test('check membersCanPerformRunActions is not shown on user projects', async () => {
await flushPromises();

wrapper.setProps({
ownertype: 'user01',
ownername: 'username01',
});

await flushPromises();

const membersCanPerformRunActions = wrapper.find<HTMLInputElement>(
'[data-test="membersCanPerformRunActions"]'
);

expect(membersCanPerformRunActions.exists()).toBe(false);
});
Loading

0 comments on commit 532ed65

Please # to comment.