Skip to content

Commit

Permalink
BXC-4690 - Admin ui buttons for cropping (#1859)
Browse files Browse the repository at this point in the history
* Update preingest list page so that crop action works and an action to view report displays, as well as a pending message if cropping is in progress.

* allow chompb process to be set via a property, to help with the dev environment

* Add property to test file

* Use popup alert
  • Loading branch information
bbpennel authored Jan 13, 2025
1 parent 6278aa9 commit 489228b
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 28 deletions.
8 changes: 8 additions & 0 deletions static/js/admin/vue-cdr-admin/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions static/js/admin/vue-cdr-admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@babel/preset-env": "^7.25.3",
"@pinia/testing": "0.1.5",
"@testing-library/jest-dom": "^6.4.8",
"@ungap/structured-clone": "^1.2.1",
"@vitejs/plugin-vue": "^4.6.2",
"@vue/compiler-sfc": "^3.5.6",
"@vue/test-utils": "2.4.6",
Expand Down
109 changes: 92 additions & 17 deletions static/js/admin/vue-cdr-admin/src/components/chompb/preIngest.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ https://vuejs.org/guide/built-ins/teleport.html
</thead>
<template #actions="props">
<a @click.prevent="copyPath(props.rowData.projectPath)" href="#">Copy Path</a>
<template v-for="action in props.rowData.allowedActions">
<a class="is-capitalized" @click.prevent="actionPath(action, props.rowData.projectProperties.name)">
{{ capitalizeAction(action) }}
<template v-for="actionInfo in listAllowedActions(props.rowData)">
<a v-if="!actionInfo.disabled"
@click.prevent="performAction(actionInfo, props.rowData)">
{{ actionInfo.label }}
</a>
<span v-else>
{{ actionInfo.label }}
</span>
</template>
</template>
</data-table>
Expand All @@ -36,6 +40,7 @@ https://vuejs.org/guide/built-ins/teleport.html
<script>
import DataTable from 'datatables.net-vue3';
import DataTablesCore from 'datatables.net-bm';
import axios from 'axios';

DataTable.use(DataTablesCore);

Expand Down Expand Up @@ -91,19 +96,100 @@ export default {

copyMsgClass() {
return this.copy_error ? 'is-danger' : 'is-success';
},

actionMapping() {
return {
'velocicroptor_action': {
'jobName': 'velocicroptor',
'action': 'action',
'label': 'Crop color bars',
'confirm': true,
'confirmMessage': 'Are you sure you want to crop color bars for this project?',
'disabled': false,
'method': 'post'
},
'velocicroptor_processing_results': {
'jobName': 'velocicroptor',
'action': 'processing_results',
'label': 'View crop report',
'confirm': false,
'disabled': false,
'method': 'link'
},
'velocicroptor_pending': {
'jobName': 'velocicroptor',
'action': 'pending',
'label': 'Crop in progress',
'confirm': false,
'disabled': true
}
};
}
},

methods: {
actionPath(action_type, action_name) {
return this.$router.push(`/admin/chompb/${action_type}/${action_name}`);
performAction(action_info, row_data) {
let projectName = row_data.projectProperties.name;
// Action requires confirmation, exiting early if the user cancels
if (action_info.confirm && !confirm(action_info.confirmMessage)) {
return;
}
if (action_info.action === 'action') {
Object.assign(row_data.processingJobs, { velocicroptor: { status: 'pending' } });
}
let actionUrl = `/admin/chompb/project/${projectName}/${action_info.action}/${action_info.jobName}`;
if (action_info.method === 'post' || action_info.method === 'get') {
axios({
method: action_info.method,
url: actionUrl
}).then((response) => {
console.log("Successfully triggered action", actionUrl);
this.copy_error = false;
this.copy_msg = `"${action_info.label}" action successfully triggered for project: ${projectName}`;
this.clearCopyMessage();
}).catch((error) => {
this.copy_error = true;
this.copy_msg = `Error encountered while performing action" ${action_info.label}" for project: ${projectName}`;
console.log("Error encountered while performing action", error);
this.clearCopyMessage();
});
} else if (action_info.method === 'link') {
this.$router.push(actionUrl);
}
},

getActionMapping(action_name) {
return structuredClone(this.actionMapping[action_name]);
},

listAllowedActions(row_data) {
let resultActions = [];
let processingJobs = row_data.processingJobs;
if (row_data.allowedActions.indexOf('crop_color_bars') > -1) {
let processingResult = processingJobs['velocicroptor'];
let processingStatus = processingResult ? processingResult.status : '';

// if status is completed, then allow processing again and allow viewing the report
if (processingStatus === 'completed') {
resultActions.push(this.getActionMapping('velocicroptor_action'));
resultActions.push(this.getActionMapping('velocicroptor_processing_results'));
} else if (processingStatus === 'pending') {
// if status is pending, then display "Cropping in progress"
resultActions.push(this.getActionMapping('velocicroptor_pending'));
} else {
// if no status or any other status, then allow processing
resultActions.push(this.getActionMapping('velocicroptor_action'));
}
}
return resultActions;
},

clearCopyMessage() {
setTimeout(() => {
this.copy_error = false;
this.copy_msg = '';
}, 4000);
}, 5000);
},

async copyPath(project_path) {
Expand All @@ -117,17 +203,6 @@ export default {
console.error('Failed to copy: ', err);
}
this.clearCopyMessage();
},

/**
* For some reason the first word in an action doesn't capitalize correctly
* using CSS, though subsequent words do. So, just up case the first letter in the string.
* @param action
* @returns {*}
*/
capitalizeAction(action) {
let text = action.replaceAll('_', ' ');
return text.charAt(0).toUpperCase() + text.slice(1);
}
}
}
Expand Down
106 changes: 98 additions & 8 deletions static/js/admin/vue-cdr-admin/tests/unit/chompb/preIngest.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { shallowMount } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import preIngest from '@/components/chompb/preIngest.vue';
import moxios from 'moxios';
import structuredClone from '@ungap/structured-clone';

global.structuredClone = structuredClone;

let wrapper;
let mockRouter;

const project_info = [
{
Expand All @@ -17,8 +22,9 @@ const project_info = [
"projectSource": "files"
},
"status": "sources_mapped",
"allowedActions": ["color_bar_crop", "color_bar_report"],
"projectPath": "/test_path_one/file_source_test"
"allowedActions": ["crop_color_bars"],
"projectPath": "/test_path_one/file_source_test",
"processingJobs" : {}
},
{
"projectProperties" : {
Expand All @@ -34,27 +40,111 @@ const project_info = [
},
"status": "sources_mapped",
"allowedActions": [],
"projectPath": "/test_path_two/file_source_test"
"projectPath": "/test_path_two/file_source_test",
"processingJobs" : {}
}
];

describe('preIngest.vue', () => {
beforeEach(() => {
wrapper = shallowMount(preIngest, {
function setupWrapper(dataSet) {
mockRouter = {
push: jest.fn(),
};

wrapper = mount(preIngest, {
global: {
stubs: {
teleport: true
},
mocks: {
$router: mockRouter,
}
},
data() {
return {
dataSet: project_info
dataSet: dataSet
}
}
});
}

beforeEach(() => {
moxios.install();
});

afterEach(function () {
moxios.uninstall();
});

it("contains a table of projects", () => {
expect(wrapper.findComponent({ name: 'dataTable' }).exists()).toBe(true);
setupWrapper(project_info);

expect(wrapper.find('.datatable').exists()).toBe(true);
let rows = wrapper.findAll('.datatable tbody tr');
let actions1 = rows[0].findAll('a');
expect(actions1[0].text()).toBe('Copy Path');
expect(actions1[1].text()).toBe('Crop color bars');
expect(actions1.length).toBe(2);
let actions2 = rows[1].findAll('a');
expect(actions2[0].text()).toBe('Copy Path');
expect(actions2.length).toBe(1);
});

it("shows link to report if job is completed", async () => {
let updatedInfo = structuredClone(project_info);
updatedInfo[0].processingJobs['velocicroptor'] = { 'status' : 'completed' };
setupWrapper(updatedInfo);

let rows = wrapper.findAll('.datatable tbody tr');
let actions1 = rows[0].findAll('a');
expect(actions1[0].text()).toBe('Copy Path');
expect(actions1[1].text()).toBe('Crop color bars');
expect(actions1[2].text()).toBe('View crop report');
expect(actions1.length).toBe(3);

await actions1[2].trigger('click');

expect(mockRouter.push).toHaveBeenCalledWith('/admin/chompb/project/file_source_test/processing_results/velocicroptor');
});

it("clicking on the crop button causes request to be made", (done) => {
// suppressing error spam from jsdom when making http requests with moxios/axios
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

setupWrapper(project_info);

moxios.stubRequest(`/admin/chompb/project/file_source_test/action/velocicroptor`, {
status: 200,
response: JSON.stringify({'action' : 'Start cropping for project file_source_test'})
});

// Mock window.confirm
const confirmMock = jest.spyOn(window, 'confirm');

// Mock return values for confirm
confirmMock.mockImplementationOnce(() => true); // Simulate "Yes" click


moxios.wait(async () => {
let rows = wrapper.findAll('.datatable tbody tr');
let actions1 = rows[0].findAll('a');
expect(actions1[1].text()).toBe('Crop color bars');
await actions1[1].trigger('click');

moxios.wait(() => {
let request = moxios.requests.mostRecent();

expect(request.config.method).toEqual('post');
expect(window.confirm).toHaveBeenCalledWith('Are you sure you want to crop color bars for this project?');
// crop option should have changed from a link to a span
rows = wrapper.findAll('.datatable tbody tr');
let actions2 = rows[0].findAll('span');
expect(actions2[0].text()).toBe('Crop in progress');

consoleErrorSpy.mockRestore();
confirmMock.mockRestore();
done();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ describe('velocicroptorReport.vue', () => {

it("contains a data tables", () => {
expect(wrapper.findComponent({ name: 'dataTable' }).exists()).toBe(true);
console.log(wrapper.html());
});

it("populates CSV download link", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class ChompbPreIngestService {
private Path baseProjectsPath;
private String serviceKeyPath;
private String serviceUser;
private String chompbCommand = "chompb";
private static final Set<String> VALID_FILENAMES = Set.of("data.json", "data.csv");
private ExecutorService executorService;

Expand All @@ -39,7 +40,7 @@ public class ChompbPreIngestService {
public String getProjectLists(AgentPrincipals agent) {
assertHasPermission(agent);

return executeChompbCommand("chompb", "-w", baseProjectsPath.toAbsolutePath().toString(), "list_projects");
return executeChompbCommand(chompbCommand, "-w", baseProjectsPath.toAbsolutePath().toString(), "list_projects");
}

/**
Expand All @@ -52,7 +53,7 @@ public void startCropping(AgentPrincipals agent, String projectName, String emai
assertHasPermission(agent);
log.info("Starting cropping for project {} for user {}", projectName, agent.getUsername());

executeBackgroundCommand("chompb", "process_source_files",
executeBackgroundCommand(chompbCommand, "process_source_files",
"--action", "velocicroptor",
"-w", baseProjectsPath.resolve(projectName).toAbsolutePath().toString(),
"-k", serviceKeyPath,
Expand Down Expand Up @@ -161,4 +162,8 @@ public void setServiceUser(String serviceUser) {
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}

public void setChompbCommand(String chompbCommand) {
this.chompbCommand = chompbCommand;
}
}
1 change: 1 addition & 0 deletions web-admin-app/src/main/webapp/WEB-INF/service-context.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
<property name="serviceKeyPath" value="${boxctron.service.key}" />
<property name="serviceUser" value="${boxctron.service.user}" />
<property name="executorService" ref="chompbCommandExecutor" />
<property name="chompbCommand" value="${chompb.command}" />
</bean>

<bean class="edu.unc.lib.boxc.web.common.view.CDRViewResolver" p:suffix=".jsp">
Expand Down
1 change: 1 addition & 0 deletions web-admin-app/src/test/resources/server.properties
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ repo.dir=/tmp/deposit
spoofing.enabled=false
spoofing.emailSuffix=@example.com
chompb.projects.basePath=/tmp/projects
chompb.command=chompb
boxctron.service.key=/path/to/key
boxctron.service.user=user

0 comments on commit 489228b

Please sign in to comment.