Enhance job and subjob schemas with name field and text indexing
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good

This commit adds a `name` field to both the job and subjob schemas, allowing for optional naming of jobs and subjobs. It updates the text indexing to include the new `name` field, improving search capabilities. Additionally, a utility function is introduced to retrieve the GCode file name based on its reference, ensuring that job names are dynamically generated based on associated GCode files. These changes enhance the overall functionality and usability of the job management system.
This commit is contained in:
Tom Butcher 2026-11-08 23:40:37 +00:00
parent 2453789343
commit 964e95019d
2 changed files with 80 additions and 2 deletions

View File

@ -10,10 +10,12 @@ import {
deleteObject,
} from '../../database.js';
import { subJobModel } from './subjob.schema.js';
import { gcodeFileModel } from './gcodefile.schema.js';
const jobSchema = new mongoose.Schema(
{
_reference: { type: String, default: () => generateId()() },
name: { type: String, required: false },
state: {
type: { required: true, type: String },
progress: { type: Number, required: false },
@ -40,7 +42,7 @@ const jobSchema = new mongoose.Schema(
{ timestamps: true }
);
jobSchema.index({ 'state.type': 'text' });
jobSchema.index({ name: 'text', 'state.type': 'text' });
const rollupConfigs = [
{
@ -94,6 +96,19 @@ jobSchema.statics.history = async function (from, to) {
const normalizeId = (value) => value?._id ?? value;
const gcodeFileNameFrom = async (gcodeFileRef) => {
if (gcodeFileRef && typeof gcodeFileRef === 'object' && typeof gcodeFileRef.name === 'string') {
const populatedName = gcodeFileRef.name.trim();
if (populatedName) return populatedName;
}
const gcodeFileId = normalizeId(gcodeFileRef);
if (!gcodeFileId) return '';
const gcodeFile = await gcodeFileModel.findById(gcodeFileId).select('name').lean();
return typeof gcodeFile?.name === 'string' ? gcodeFile.name.trim() : '';
};
jobSchema.statics.recalculate = async function (job, user) {
const jobId = job._id || job;
if (!jobId || job?.state?.type !== 'draft') {
@ -103,6 +118,25 @@ jobSchema.statics.recalculate = async function (job, user) {
const quantity = job.quantity || 1;
const printers = (job.printers || []).map((printer) => normalizeId(printer));
const gcodeFile = normalizeId(job.gcodeFile);
const gcodeFileName = await gcodeFileNameFrom(job.gcodeFile);
const jobName = gcodeFileName ? `${quantity} ${gcodeFileName}` : null;
const subJobName = gcodeFileName || null;
if ((job.name ?? null) !== jobName) {
const jobResult = await editObject({
model: this,
id: jobId,
updateData: {
name: jobName,
updatedAt: new Date(),
},
user,
recalculate: false,
});
if (jobResult?.error) {
throw jobResult;
}
}
const existingSubJobs = await listObjects({
model: subJobModel,
@ -127,6 +161,7 @@ jobSchema.statics.recalculate = async function (job, user) {
const printer = printers[printerCount];
const subJobUpdateData = {
updatedAt: new Date(),
name: subJobName,
printer,
gcodeFile,
number: subJobNumber,
@ -188,3 +223,24 @@ jobSchema.virtual('id').get(function () {
jobSchema.set('toJSON', { virtuals: true });
export const jobModel = mongoose.model('job', jobSchema);
const replaceTextIndex = async (model, field) => {
try {
const indexes = await model.collection.indexes();
const textIndex = indexes.find((index) => Object.values(index.key || {}).includes('text'));
const weights = textIndex?.weights;
const includesField = weights != null && Object.prototype.hasOwnProperty.call(weights, field);
if (textIndex && !includesField) {
await model.collection.dropIndex(textIndex.name);
}
await model.createIndexes();
} catch {
// Collection may not exist until Mongo is connected.
}
};
if (mongoose.connection.readyState === 1) {
replaceTextIndex(jobModel, 'name');
} else {
mongoose.connection.once('open', () => replaceTextIndex(jobModel, 'name'));
}

View File

@ -4,6 +4,7 @@ const { Schema } = mongoose;
const subJobSchema = new mongoose.Schema({
_reference: { type: String, default: () => generateId()() },
name: { type: String, required: false },
printer: {
type: Schema.Types.ObjectId,
ref: 'printer',
@ -43,7 +44,7 @@ const subJobSchema = new mongoose.Schema({
finishedAt: { required: false, type: Date, default: null },
});
subJobSchema.index({ moonrakerJobId: 'text', 'state.type': 'text' });
subJobSchema.index({ name: 'text', moonrakerJobId: 'text', 'state.type': 'text' });
subJobSchema.virtual('id').get(function () {
return this._id;
@ -52,3 +53,24 @@ subJobSchema.virtual('id').get(function () {
subJobSchema.set('toJSON', { virtuals: true });
export const subJobModel = mongoose.model('subJob', subJobSchema);
const replaceTextIndex = async (model, field) => {
try {
const indexes = await model.collection.indexes();
const textIndex = indexes.find((index) => Object.values(index.key || {}).includes('text'));
const weights = textIndex?.weights;
const includesField = weights != null && Object.prototype.hasOwnProperty.call(weights, field);
if (textIndex && !includesField) {
await model.collection.dropIndex(textIndex.name);
}
await model.createIndexes();
} catch {
// Collection may not exist until Mongo is connected.
}
};
if (mongoose.connection.readyState === 1) {
replaceTextIndex(subJobModel, 'name');
} else {
mongoose.connection.once('open', () => replaceTextIndex(subJobModel, 'name'));
}