• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

statuscompliance / status-backend / 14548523079

19 Apr 2025 11:05AM UTC coverage: 35.962% (-2.2%) from 38.115%
14548523079

Pull #144

github

web-flow
Merge 6a9866bd5 into 6cc29666b
Pull Request #144: feat(catalog): added draft objects

217 of 710 branches covered (30.56%)

Branch coverage included in aggregate %.

15 of 124 new or added lines in 4 files covered. (12.1%)

3 existing lines in 2 files now uncovered.

613 of 1598 relevant lines covered (38.36%)

3.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.21
/src/controllers/control.controller.js
1
import { models } from '../models/models.js';
2
import { methods } from '../config/grafana.js';
3
import { checkRequiredProperties } from '../utils/checkRequiredProperties.js';
4

5
export const getControls = async (req, res) => {
13✔
6
  const rows = await models.Control.findAll();
×
7
  res.json(rows);
×
8
};
9

10
export const getControl = async (req, res) => {
13✔
11
  const row = await models.Control.findByPk(req.params.id);
×
12

13
  if (!row)
×
14
    return res.status(404).json({
×
15
      message: 'Control not found',
16
    });
17

18
  res.status(200).json(row);
×
19
};
20

21
export const getCatalogControls = async (req, res) => {
13✔
22
  const rows = await models.Control.findAll({
×
23
    where: {
24
      catalogId: req.params.catalogId,
25
    },
26
  });
27

28
  res.json(rows);
×
29
};
30

31
export const createControl = async (req, res) => {
13✔
NEW
32
  try {
×
33
    const {
34
      name,
35
      description,
36
      period,
37
      startDate,
38
      endDate,
39
      mashupId,
40
      catalogId,
41
      params, // Should include endpoint and threshold at least
NEW
42
    } = req.body;
×
43
    
NEW
44
    const {validation, textError} = checkRequiredProperties(params, ['endpoint', 'threshold']);
×
45

NEW
46
    if(!validation) {
×
NEW
47
      return res.status(400).json({error: textError});
×
48
    }
49
    
NEW
50
    let formattedStartDate = null;
×
NEW
51
    if (startDate) {
×
NEW
52
      formattedStartDate = new Date(startDate);
×
NEW
53
      if (isNaN(formattedStartDate.getTime())) {
×
NEW
54
        return res.status(400).json({ error: 'Invalid startDate' });
×
55
      }
56
    }
UNCOV
57
    const formattedEndDate = endDate ? new Date(endDate) : null;
×
58

59
    const rows = await models.Control.create({
×
60
      name,
61
      description,
62
      period,
63
      startDate: formattedStartDate,
64
      endDate: formattedEndDate,
65
      mashupId,
66
      catalogId,
67
      params,
68
      status: 'finalized',
69
    });
70
    
71
    res.status(201).json({
×
72
      id: rows.id,
73
      name,
74
      description,
75
      period,
76
      formattedStartDate,
77
      formattedEndDate,
78
      mashupId,
79
      catalogId,
80
    });
81
  } catch (error) {
NEW
82
    console.error('Error creating control:', error);
×
NEW
83
    res.status(500).json({
×
84
      message: 'Error creating control',
85
      error: error.message,
86
    });
87
  }
88
};
89

90
export const updateControl = async (req, res) => {
13✔
91
  const { id } = req.params;
×
92
  const {
93
    name,
94
    description,
95
    period,
96
    startDate,
97
    endDate,
98
    mashupId,
99
    catalogId,
100
    params,
101
    status,
UNCOV
102
  } = req.body;
×
103
  
NEW
104
  try {
×
NEW
105
    const currentControl = await models.Control.findByPk(id);
×
NEW
106
    if (!currentControl) {
×
NEW
107
      return res.status(404).json({ message: 'Control not found' });
×
108
    }
109
    
110
    // No se permite cambiar un control finalizado a borrador
NEW
111
    if (currentControl.status === 'finalized' && status === 'draft') {
×
NEW
112
      return res.status(400).json({ 
×
113
        message: 'Cannot change status from finalized to draft' 
114
      });
115
    }
116
    
117
    // Si es un control finalizado, validar los params
NEW
118
    if (status === 'finalized' || (!status && currentControl.status === 'finalized')) {
×
NEW
119
      const {validation, textError} = checkRequiredProperties(params || currentControl.params, ['endpoint', 'threshold']);
×
NEW
120
      if (!validation) {
×
NEW
121
        return res.status(400).json({error: textError});
×
122
      }
123
    }
124
    
NEW
125
    const formattedStartDate = startDate ? new Date(startDate) : currentControl.startDate;
×
NEW
126
    const formattedEndDate = endDate ? new Date(endDate) : currentControl.endDate;
×
127

NEW
128
    await models.Control.update(
×
129
      {
130
        name,
131
        description,
132
        period,
133
        startDate: formattedStartDate,
134
        endDate: formattedEndDate,
135
        mashupId,
136
        catalogId,
137
        params,
138
        status,
139
      },
140
      {
141
        where: {
142
          id,
143
        },
144
      }
145
    );
146

NEW
147
    const row = await models.Control.findByPk(id);
×
NEW
148
    res.status(200).json(row);
×
149
  } catch (error) {
NEW
150
    res.status(500).json({ 
×
151
      message: `Failed to update control, error: ${error.message}` 
152
    });
153
  }
154
};
155

156
export const deleteControl = async (req, res) => {
13✔
157
  try {
×
158
    const { id } = req.params;
×
159
    const deletedCount = await models.Control.destroy({
×
160
      where: { id },
161
    });
162

163
    if (deletedCount === 0) {
×
164
      return res.status(404).json({ message: 'Control not found' });
×
165
    }
166

167
    return res.status(204).send();
×
168
  } catch (error) {
169
    console.error('Error deleting control:', error);
×
170
    return res.status(500).json({
×
171
      message: 'Error deleting control',
172
      error: error.message,
173
    });
174
  }
175
};
176

177
export async function addPanelToControl(req, res) {
178
  const { id, panelId } = req.params;
×
179

180
  const { dashboardUid } = req.body;
×
181

182
  try {
×
183
    const panel = await models.Panel.create({
×
184
      id: panelId,
185
      controlId: id,
186
      dashboardUid: dashboardUid,
187
    });
188
    res.status(201).json({
×
189
      message: 'Panel added to control',
190
      data: panel,
191
    });
192
  } catch (error) {
193
    res.status(500).json({
×
194
      message: 'Error adding panel to control',
195
      error: error.message,
196
    });
197
  }
198
}
199

200
export async function getPanelsByControlId(req, res) {
201
  const { id } = req.params;
×
202

203
  try {
×
204
    const panels = await models.Panel.findAll({
×
205
      where: {
206
        controlId: id,
207
      },
208
    });
209
    let panelsDTO = [];
×
210

211
    // THIS MUST BE CACHED AND REFACTORED
212
    for (let panel of panels) {
×
213
      panel = panel.dataValues;
×
214
      let panelDTO = {};
×
215
      if (Object.prototype.hasOwnProperty.call(panel, 'dashboardUid')) {
×
216
        const dashboardUid = panel.dashboardUid;
×
217
        const dashboardResponse =
218
                    await methods.dashboard.getDashboardByUID(dashboardUid);
×
219
        const actualDashboard = dashboardResponse.data.dashboard;
×
220
        const panelElement = actualDashboard.panels.find(
×
221
          (e) => e.id == panel.id
×
222
        );
223
        panelDTO = {
×
224
          ...panel,
225
          title: panelElement.title,
226
          type: panelElement.type,
227
          sqlQuery: panelElement.targets[0].rawSql,
228
          table: panelElement.targets[0].table,
229
          displayName: panelElement.targets[0].alias,
230
          gridPos: panelElement.gridPos,
231
        };
232
        panelsDTO.push(panelDTO);
×
233
      }
234
    }
235
    res.status(200).json(panelsDTO);
×
236
  } catch (error) {
237
    if (error.response) {
×
238
      const { status, statusText } = error.response;
×
239
      return res.status(status).json({
×
240
        message: statusText,
241
        error: error,
242
      });
243
    } else {
244
      return res.status(500).json({
×
245
        message:
246
                    'Failed to get panels from control, error in Grafana API',
247
        error: error.message,
248
      });
249
    }
250
  }
251
}
252

253
export async function deletePanelFromControl(req, res) {
254
  const { id, panelId } = req.params;
×
255

256
  try {
×
257
    await models.Panel.destroy({
×
258
      where: {
259
        controlId: id,
260
        id: panelId,
261
      },
262
    });
263
    res.status(204).json({
×
264
      message: 'Panel deleted from control',
265
    });
266
  } catch (error) {
267
    res.status(500).json({
×
268
      message: 'Error deleting panel from control',
269
      error: error.message,
270
    });
271
  }
272
}
273

274
// Draft controls
275

276
export const getDraftControls = async (req, res) => {
13✔
NEW
277
  try {
×
NEW
278
    const controls = await models.Control.findAll({
×
279
      where: {
280
        status: 'draft'
281
      }
282
    });
NEW
283
    res.status(200).json(controls);
×
284
  } catch (error) {
NEW
285
    res.status(500).json({ 
×
286
      message: `Failed to retrieve draft controls, error: ${error.message}` 
287
    });
288
  }
289
};
290

291
export const getDraftControlsByCatalogId = async (req, res) => {
13✔
NEW
292
  try {
×
NEW
293
    const { catalogId } = req.params;
×
294
    
295
    // Check if catalog exists
NEW
296
    const catalog = await models.Catalog.findByPk(catalogId);
×
NEW
297
    if (!catalog) {
×
NEW
298
      return res.status(404).json({ message: 'Catalog not found' });
×
299
    }
300
    
NEW
301
    const controls = await models.Control.findAll({
×
302
      where: {
303
        catalogId,
304
        status: 'draft'
305
      }
306
    });
307
    
NEW
308
    res.status(200).json(controls);
×
309
  } catch (error) {
NEW
310
    res.status(500).json({ 
×
311
      message: `Failed to retrieve draft controls, error: ${error.message}` 
312
    });
313
  }
314
};
315

316
export const createDraftControl = async (req, res) => {
13✔
317
  const {
318
    name,
319
    description,
320
    startDate,
321
    endDate,
322
    period,
323
    mashupId,
324
    catalogId,
325
    params,
NEW
326
  } = req.body;
×
327
  
NEW
328
  if (!name || !catalogId) {
×
NEW
329
    return res.status(400).json({
×
330
      error: 'Missing required fields for draft control: name and catalogId'
331
    });
332
  }
333
  
NEW
334
  try {
×
335
    // Check if catalog exists
NEW
336
    const catalog = await models.Catalog.findByPk(catalogId);
×
NEW
337
    if (!catalog) {
×
NEW
338
      return res.status(404).json({ error: 'Catalog not found' });
×
339
    }
340
    
341
    // Check if catalog is a draft
NEW
342
    if (catalog.status !== 'draft') {
×
NEW
343
      return res.status(400).json({
×
344
        error: 'Draft controls can only be added to draft catalogs'
345
      });
346
    }
347
    
NEW
348
    const rows = await models.Control.create({
×
349
      name,
350
      description: description || '',
×
351
      period: period || 'MONTHLY',
×
352
      startDate: startDate ? new Date(startDate) : new Date(),
×
353
      endDate: endDate ? new Date(endDate) : null,
×
354
      mashupId: mashupId || '',
×
355
      catalogId,
356
      params: params || {},
×
357
      status: 'draft',
358
    });
359
    
NEW
360
    res.status(201).json(rows);
×
361
  } catch (error) {
NEW
362
    res.status(500).json({
×
363
      message: `Failed to create draft control, error: ${error.message}`
364
    });
365
  }
366
};
367

368
export const finalizeControl = async (req, res) => {
13✔
NEW
369
  try {
×
NEW
370
    const { id } = req.params;
×
371
    
NEW
372
    const currentControl = await models.Control.findByPk(id);
×
NEW
373
    if (!currentControl) {
×
NEW
374
      return res.status(404).json({ message: 'Control not found' });
×
375
    }
376
    
NEW
377
    if (currentControl.status !== 'draft') {
×
NEW
378
      return res.status(400).json({ message: 'Only draft controls can be finalized' });
×
379
    }
380
    
381
    // Check if associated catalog is finalized
NEW
382
    const catalog = await models.Catalog.findByPk(currentControl.catalogId);
×
NEW
383
    if (!catalog) {
×
NEW
384
      return res.status(404).json({ message: 'Associated catalog not found' });
×
385
    }
386
    
NEW
387
    if (catalog.status !== 'finalized') {
×
NEW
388
      return res.status(400).json({ 
×
389
        message: 'Cannot finalize a control that belongs to a draft catalog' 
390
      });
391
    }
392
    
393
    // Check required properties for finalized controls
NEW
394
    const {validation, textError} = checkRequiredProperties(
×
395
      currentControl.params, 
396
      ['endpoint', 'threshold']
397
    );
398
    
NEW
399
    if (!validation) {
×
NEW
400
      return res.status(400).json({
×
401
        error: `Cannot finalize control: ${textError}`
402
      });
403
    }
404
    
NEW
405
    const updatedControl = await models.Control.update(
×
406
      {
407
        status: 'finalized',
408
      },
409
      {
410
        where: {
411
          id,
412
        },
413
        returning: true,
414
      }
415
    );
416
    
NEW
417
    res.status(200).json(updatedControl[1][0]);
×
418
  } catch (error) {
NEW
419
    res.status(500).json({ 
×
420
      message: `Failed to finalize control, error: ${error.message}` 
421
    });
422
  }
423
};
424

425
// Method to finalize all draft controls in a catalog
426
export const finalizeControlsByCatalogId = async (catalogId) => {
13✔
NEW
427
  try {
×
428
    // Get draft controls
NEW
429
    const draftControls = await models.Control.findAll({
×
430
      where: {
431
        catalogId,
432
        status: 'draft'
433
      }
434
    });
435
    
436
    // Update valid controls to finalized
NEW
437
    let updatedControls = {};
×
NEW
438
    if (draftControls.length > 0) {
×
NEW
439
      updatedControls = await models.Control.update(
×
440
        { status: 'finalized' },
441
        {
442
          where: {
NEW
443
            id: draftControls.map(control => control.id)
×
444
          }
445
        }
446
      );
447
    }
448
    
NEW
449
    return updatedControls;
×
450
  } catch (error) {
NEW
451
    console.error('Error finalizing controls:', error);
×
NEW
452
    throw error;
×
453
  }
454
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc