DEV Community

Rahul
Rahul

Posted on

Testcraft Studio frontend 15: src/baseline.js

import { baselineInputIssues, documentInputIssues } from './access.js';
import { canApprove, bumpVersion, log, timestamp, uid, resolveSelection } from './store.jsx';

const approved = baseline => baseline && ['Active', 'Approved'].includes(baseline.status);
const sourcesFor = (state, ids) => ids.map(id => {
  const doc = state.documents.find(item => item.id === id);
  if (!doc) throw new Error('A baseline document is no longer available.');
  return { id, revision: doc.revision, checksum: doc.checksum || 'synthetic-sample' };
});

export function requestBaseline(state, values, documents, workspaceId = '', parentId = '') {
  if (!state.session.authenticated) throw new Error('Sign in before submitting a baseline.');
  const parent = state.baselines.find(item => item.id === parentId);
  const workspace = state.workspaces.find(item => item.id === workspaceId && item.team === state.session.team);
  if (parentId && (!approved(parent) || parent.team !== state.session.team)) throw new Error('Choose an approved System Baseline to edit.');
  if (state.candidates.some(item => item.status === 'Pending review' && (parentId ? item.parentId === parentId : workspaceId && item.workspaceId === workspaceId))) throw new Error('An approval request is already pending for this baseline or workspace.');
  const issues = parent ? documentInputIssues(documents) : baselineInputIssues(state, values.ref, documents);
  if (issues.length) throw new Error(issues.join(' '));
  const sourceIds = documents.map(doc => doc.id);
  if (parent && JSON.stringify([...parent.sourceIds].sort()) === JSON.stringify([...sourceIds].sort()) && parent.description === values.description && (parent.scope || '') === (values.scope || '')) throw new Error('Add or remove a document, or update the baseline details.');
  documents.forEach(doc => { if (!state.documents.some(item => item.id === doc.id)) state.documents.push({ ...doc, status: 'Ready' }); });
  const baseline = { id: uid('baseline'), ref: parent?.ref || values.ref.trim(), team: state.session.team, description: values.description || '', scope: values.scope || '', version: parent ? `${Number(parent.version.split('.')[0]) + 1}.0` : '1.0', status: 'Pending approval', sourceIds, rules: (parent?.rules || []).filter(rule => sourceIds.includes(rule.source)), incorporatedCRs: [...parent?.incorporatedCRs || []], parentId: parent?.id || null, createdAt: timestamp() };
  state.baselines.push(baseline);
  const request = { id: uid('candidate'), type: parent ? 'edit' : 'create', baselineId: baseline.id, parentId: parent?.id || null, workspaceId: workspace?.id || '', team: state.session.team, ref: baseline.ref, description: baseline.description, crIds: [], revision: 1, status: 'Pending review', createdAt: timestamp(), decisions: [], manifest: { baseline: structuredClone(baseline), sources: sourcesFor(state, sourceIds), previousSourceIds: [...parent?.sourceIds || []] } };
  state.candidates.unshift(request);
  if (workspace) workspace.pendingBaselineId = baseline.id;
  log(state, `Submitted System Baseline ${parent ? 'edit' : 'creation'}: ${baseline.ref}`);
  return request;
}

export function approvalBlockers(state, request) {
  if (!request) return [];
  if (!request.manifest?.baseline) {
    const issues = [...resolveSelection(state, request.parentId, request.crIds || []).issues];
    if ((request.crIds || []).some(id => !state.crs.find(cr => cr.id === id)?.changes.length)) issues.push('Add source-linked changes to each selected CR before requesting a baseline update.');
    for (const source of request.manifest?.sources || []) {
      const doc = state.documents.find(item => item.id === source.id && item.team === request.team);
      if (source.revision !== undefined && (!doc || doc.revision !== source.revision || (doc.checksum || 'synthetic-sample') !== source.checksum)) issues.push('A submitted document changed or is unavailable. Submit a fresh request.');
    }
    for (const cr of request.manifest?.changes || []) if (state.crs.find(item => item.id === cr.id)?.revision !== cr.revision) issues.push('A submitted CR changed. Submit a fresh merge request.');
    return [...new Set(issues)];
  }
  const issues = [];
  if (!request.manifest.sources.length) issues.push('Keep at least one baseline document.');
  for (const source of request.manifest.sources) {
    const doc = state.documents.find(item => item.id === source.id && item.team === request.team);
    if (!doc || doc.revision !== source.revision || (doc.checksum || 'synthetic-sample') !== source.checksum) issues.push('A submitted document changed or is unavailable. Submit a fresh request.');
  }
  if (request.parentId && !approved(state.baselines.find(item => item.id === request.parentId))) issues.push('The parent baseline is unavailable.');
  return [...new Set(issues)];
}

export function requestBaselineMerge(state, workspaceId, name) {
  if (!state.session.authenticated) throw new Error('Sign in before submitting a CR merge.');
  const workspace = state.workspaces.find(item => item.id === workspaceId && item.team === state.session.team);
  if (!workspace?.crIds.length) throw new Error('Select at least one CR in Knowledge versions.');
  const selection = resolveSelection(state, workspace.baselineId, workspace.crIds);
  if (selection.issues.length) throw new Error(selection.issues.join(' '));
  if (selection.changes.some(cr => !cr.changes.length)) throw new Error('Add source-linked changes to each selected CR.');
  if (state.candidates.some(item => item.workspaceId === workspace.id && item.status === 'Pending review')) throw new Error('This workspace already has a pending baseline request.');
  const ids = [...new Set([...selection.baseline.sourceIds, ...selection.diff.map(change => change.source)])];
  const docs = ids.map(id => state.documents.find(doc => doc.id === id && doc.team === workspace.team));
  if (docs.some(doc => !doc || !['Ready', 'Reviewed locally'].includes(doc.status))) throw new Error('Review every selected baseline and CR document before merging.');
  const issues = baselineInputIssues(state, name, docs);
  if (issues.length) throw new Error(issues.join(' '));
  if (name.trim().length > 60) throw new Error('Use a baseline name of 60 characters or fewer.');
  const request = { id: uid('candidate'), type: 'changes', ref: name.trim(), description: `Merge ${selection.changes.map(cr => cr.ref).join(', ')} into ${selection.baseline.ref}.`, team: workspace.team, workspaceId: workspace.id, parentId: workspace.baselineId, crIds: [...workspace.crIds], manifest: { parent: structuredClone(selection.baseline), changes: structuredClone(selection.changes), sources: sourcesFor(state, ids), previousSourceIds: [...selection.baseline.sourceIds] }, status: 'Pending review', revision: 1, createdAt: timestamp(), decisions: [] };
  state.candidates.unshift(request); log(state, `Submitted CR merge: ${request.ref}`);
  return request;
}

export function decideBaseline(state, requestId, decision, rationale = '', hash = '') {
  if (!canApprove(state)) throw new Error('Only an Administrator can approve or reject a baseline.');
  const request = state.candidates.find(item => item.id === requestId && item.team === state.session.team);
  if (!request || request.status !== 'Pending review') throw new Error('This request has already been decided.');
  if (!['Approved', 'Rejected'].includes(decision)) throw new Error('Choose an approval decision.');
  if (decision === 'Rejected' && !rationale.trim()) throw new Error('Add a reason for rejection.');
  if (decision === 'Approved' && approvalBlockers(state, request).length) throw new Error(approvalBlockers(state, request).join(' '));
  let baseline = state.baselines.find(item => item.id === request.baselineId);
  if (decision === 'Approved') {
    if (request.manifest?.baseline) {
      if (!baseline || baseline.status !== 'Pending approval') throw new Error('The submitted baseline is unavailable.');
      Object.assign(baseline, structuredClone(request.manifest.baseline), { status: 'Approved', manifestHash: hash });
    } else {
      const selection = resolveSelection(state, request.parentId, request.crIds || []);
      const snapshot = request.manifest || { parent: selection.baseline, changes: selection.changes };
      const rules = new Map(snapshot.parent.rules.map(rule => [rule.id, rule]));
      snapshot.changes.forEach(cr => cr.changes.forEach(change => { if (change.op === 'REMOVE') rules.delete(change.ruleId); else rules.set(change.ruleId, { id: change.ruleId, title: change.title, text: change.after, source: change.source }); }));
      baseline = { ...structuredClone(snapshot.parent), id: uid('baseline'), ref: request.ref, description: request.description, version: `${Number(snapshot.parent.version.split('.')[0]) + 1}.0`, parentId: request.parentId, status: 'Approved', sourceIds: [...new Set([...(snapshot.sources?.map(source => source.id) || snapshot.parent.sourceIds), ...[...rules.values()].map(rule => rule.source)])], rules: [...rules.values()], incorporatedCRs: [...new Set([...snapshot.parent.incorporatedCRs, ...request.crIds])], manifestHash: hash, createdAt: timestamp() };
      state.baselines.push(baseline); request.baselineId = baseline.id;
    }
    const workspace = state.workspaces.find(item => item.id === request.workspaceId && item.team === request.team);
    if (workspace && (workspace.pendingBaselineId === baseline.id || !workspace.baselineId || workspace.baselineId === request.parentId)) {
      const previous = state.baselines.find(item => item.id === workspace.baselineId);
      const removed = (previous?.sourceIds || []).filter(id => !baseline.sourceIds.includes(id));
      workspace.customRules = (workspace.customRules || []).filter(rule => !removed.includes(rule.source));
      workspace.baselineId = baseline.id; workspace.pendingBaselineId = ''; workspace.crIds = workspace.crIds.filter(id => !baseline.incorporatedCRs.includes(id)); workspace.requirementCount = baseline.rules.length + workspace.customRules.length; bumpVersion(state, workspace);
    }
  } else {
    if (baseline) baseline.status = 'Rejected';
    const workspace = state.workspaces.find(item => item.id === request.workspaceId);
    if (workspace?.pendingBaselineId === request.baselineId) workspace.pendingBaselineId = '';
  }
  request.status = decision;
  request.decisions.push({ decision, actor: state.session.name, at: timestamp(), rationale: rationale.trim(), revision: request.revision, manifestHash: hash });
  log(state, `${decision} System Baseline ${request.type === 'create' ? 'creation' : 'edit'}: ${request.ref}`);
  return baseline;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)