feat: inplement auth for runs and runCases
This commit is contained in:
@@ -4,6 +4,7 @@ const defineMember = require('../models/members');
|
|||||||
const defineProject = require('../models/projects');
|
const defineProject = require('../models/projects');
|
||||||
const defineFolder = require('../models/folders');
|
const defineFolder = require('../models/folders');
|
||||||
const defineCase = require('../models/cases');
|
const defineCase = require('../models/cases');
|
||||||
|
const defineRun = require('../models/runs');
|
||||||
|
|
||||||
function verifyEditableMiddleware(sequelize) {
|
function verifyEditableMiddleware(sequelize) {
|
||||||
/**
|
/**
|
||||||
@@ -195,12 +196,99 @@ function verifyEditableMiddleware(sequelize) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify user is reporter of the project by projectId
|
||||||
|
* (have to be called after verifySignedIn() middleware)
|
||||||
|
*/
|
||||||
|
async function verifyProjectReporterFromProjectId(req, res, next) {
|
||||||
|
const projectId = req.params.projectId || req.query.projectId;
|
||||||
|
if (!projectId) {
|
||||||
|
return res.status(400).json({ error: 'projectId is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isReporterRet = await isReporter(projectId, req.userId);
|
||||||
|
if (isReporterRet) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(403).json({ error: 'Forbidden' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify user is reporter of the project by runId
|
||||||
|
* (have to be called after verifySignedIn() middleware)
|
||||||
|
*/
|
||||||
|
async function verifyProjectReporterFromRunId(req, res, next) {
|
||||||
|
const Run = defineRun(sequelize, DataTypes);
|
||||||
|
|
||||||
|
const runId = req.params.runId || req.query.runId;
|
||||||
|
if (!runId) {
|
||||||
|
return res.status(400).json({ error: 'runId is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// find project id from runId
|
||||||
|
const run = await Run.findByPk(runId);
|
||||||
|
const projectId = run && run.id;
|
||||||
|
if (!projectId) {
|
||||||
|
return res.status(404).send('failed to find projectId');
|
||||||
|
}
|
||||||
|
|
||||||
|
const isReporterRet = await isReporter(projectId, req.userId);
|
||||||
|
if (isReporterRet) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(403).json({ error: 'Forbidden' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isReporter(projectId, userId) {
|
||||||
|
const Project = defineProject(sequelize, DataTypes);
|
||||||
|
const Member = defineMember(sequelize, DataTypes);
|
||||||
|
Project.hasMany(Member, { foreignKey: 'projectId' });
|
||||||
|
Member.belongsTo(Project, { foreignKey: 'projectId' });
|
||||||
|
|
||||||
|
const project = await Project.findOne({
|
||||||
|
where: { id: projectId },
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Member,
|
||||||
|
where: { userId: userId },
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (!project) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// owner has reporter or higher authority
|
||||||
|
if (project.userId === userId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = project.Members && project.Members[0];
|
||||||
|
if (member) {
|
||||||
|
const managerRoleIndex = memberRoles.findIndex((entry) => entry.uid === 'manager');
|
||||||
|
const developerRoleIndex = memberRoles.findIndex((entry) => entry.uid === 'developer');
|
||||||
|
const reporterRoleIndex = memberRoles.findIndex((entry) => entry.uid === 'reporter');
|
||||||
|
if (member.role === managerRoleIndex || member.role === developerRoleIndex || member.role === reporterRoleIndex) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
verifyProjectOwner,
|
verifyProjectOwner,
|
||||||
verifyProjectManagerFromProjectId,
|
verifyProjectManagerFromProjectId,
|
||||||
verifyProjectDeveloperFromProjectId,
|
verifyProjectDeveloperFromProjectId,
|
||||||
verifyProjectDeveloperFromFolderId,
|
verifyProjectDeveloperFromFolderId,
|
||||||
verifyProjectDeveloperFromCaseId,
|
verifyProjectDeveloperFromCaseId,
|
||||||
|
verifyProjectReporterFromProjectId,
|
||||||
|
verifyProjectReporterFromRunId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const defineMember = require('../models/members');
|
|||||||
const defineProject = require('../models/projects');
|
const defineProject = require('../models/projects');
|
||||||
const defineFolder = require('../models/folders');
|
const defineFolder = require('../models/folders');
|
||||||
const defineCase = require('../models/cases');
|
const defineCase = require('../models/cases');
|
||||||
|
const defineRun = require('../models/runs');
|
||||||
|
|
||||||
function verifyVisibleMiddleware(sequelize) {
|
function verifyVisibleMiddleware(sequelize) {
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +89,34 @@ function verifyVisibleMiddleware(sequelize) {
|
|||||||
return res.status(403).json({ error: 'Forbidden' });
|
return res.status(403).json({ error: 'Forbidden' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify user can read project by runId
|
||||||
|
* (have to be called after verifySignedIn() middleware)
|
||||||
|
*/
|
||||||
|
async function verifyProjectVisibleFromRunId(req, res, next) {
|
||||||
|
const Run = defineRun(sequelize, DataTypes);
|
||||||
|
|
||||||
|
const runId = req.params.runId || req.query.runId;
|
||||||
|
if (!runId) {
|
||||||
|
return res.status(400).json({ error: 'runId is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// find project id from runId
|
||||||
|
const run = await Run.findByPk(runId);
|
||||||
|
const projectId = run && run.id;
|
||||||
|
if (!projectId) {
|
||||||
|
return res.status(404).send('failed to find projectId');
|
||||||
|
}
|
||||||
|
|
||||||
|
const isVisble = await isVisible(projectId, req.userId);
|
||||||
|
if (isVisble) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(403).json({ error: 'Forbidden' });
|
||||||
|
}
|
||||||
|
|
||||||
async function isVisible(projectId, userId) {
|
async function isVisible(projectId, userId) {
|
||||||
const Project = defineProject(sequelize, DataTypes);
|
const Project = defineProject(sequelize, DataTypes);
|
||||||
const Member = defineMember(sequelize, DataTypes);
|
const Member = defineMember(sequelize, DataTypes);
|
||||||
@@ -128,6 +157,7 @@ function verifyVisibleMiddleware(sequelize) {
|
|||||||
verifyProjectVisibleFromProjectId,
|
verifyProjectVisibleFromProjectId,
|
||||||
verifyProjectVisibleFromFolderId,
|
verifyProjectVisibleFromFolderId,
|
||||||
verifyProjectVisibleFromCaseId,
|
verifyProjectVisibleFromCaseId,
|
||||||
|
verifyProjectVisibleFromRunId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,14 @@ const { DataTypes, Op } = require('sequelize');
|
|||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
const RunCase = defineRunCase(sequelize, DataTypes);
|
const RunCase = defineRunCase(sequelize, DataTypes);
|
||||||
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
|
const { verifyProjectReporterFromRunId } = require('../../middleware/verifyEditable')(sequelize);
|
||||||
|
|
||||||
router.post('/bulkdelete', async (req, res) => {
|
router.post('/bulkdelete', verifySignedIn, verifyProjectReporterFromRunId, async (req, res) => {
|
||||||
const recordsToDelete = req.body;
|
const recordsToDelete = req.body;
|
||||||
|
|
||||||
|
// TODO Instead of receiving a combination of runId and caseId from frontend,
|
||||||
|
// receives only an array of caseId and constructs a pair from the query parameter runId
|
||||||
try {
|
try {
|
||||||
const existingRunCases = await RunCase.findAll({
|
const existingRunCases = await RunCase.findAll({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ const defineRunCase = require('../../models/runCases');
|
|||||||
const { DataTypes, Op } = require('sequelize');
|
const { DataTypes, Op } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
|
const { verifyProjectReporterFromRunId } = require('../../middleware/verifyEditable')(sequelize);
|
||||||
const RunCase = defineRunCase(sequelize, DataTypes);
|
const RunCase = defineRunCase(sequelize, DataTypes);
|
||||||
|
|
||||||
router.post('/bulknew', async (req, res) => {
|
router.post('/bulknew', verifySignedIn, verifyProjectReporterFromRunId, async (req, res) => {
|
||||||
const recordsToCreate = req.body;
|
const recordsToCreate = req.body;
|
||||||
|
|
||||||
|
// TODO Instead of receiving a combination of runId and caseId from frontend,
|
||||||
|
// receives only an array of caseId and constructs a pair from the query parameter runId
|
||||||
try {
|
try {
|
||||||
const existingRunCases = await RunCase.findAll({
|
const existingRunCases = await RunCase.findAll({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ const defineRunCase = require('../../models/runCases');
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
|
const { verifyProjectReporterFromRunId } = require('../../middleware/verifyEditable')(sequelize);
|
||||||
const RunCase = defineRunCase(sequelize, DataTypes);
|
const RunCase = defineRunCase(sequelize, DataTypes);
|
||||||
|
|
||||||
router.delete('/', async (req, res) => {
|
router.delete('/', verifySignedIn, verifyProjectReporterFromRunId, async (req, res) => {
|
||||||
const runId = req.query.runId;
|
const runId = req.query.runId;
|
||||||
const caseId = req.query.caseId;
|
const caseId = req.query.caseId;
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ const defineRunCase = require('../../models/runCases');
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
|
const { verifyProjectReporterFromRunId } = require('../../middleware/verifyEditable')(sequelize);
|
||||||
const RunCase = defineRunCase(sequelize, DataTypes);
|
const RunCase = defineRunCase(sequelize, DataTypes);
|
||||||
|
|
||||||
router.put('/', async (req, res) => {
|
router.put('/', verifySignedIn, verifyProjectReporterFromRunId, async (req, res) => {
|
||||||
const runId = req.query.runId;
|
const runId = req.query.runId;
|
||||||
const caseId = req.query.caseId;
|
const caseId = req.query.caseId;
|
||||||
const status = req.query.status;
|
const status = req.query.status;
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ const defineRunCase = require('../../models/runCases');
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
|
const { verifyProjectVisibleFromRunId } = require('../../middleware/verifyVisible')(sequelize);
|
||||||
const RunCase = defineRunCase(sequelize, DataTypes);
|
const RunCase = defineRunCase(sequelize, DataTypes);
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', verifySignedIn, verifyProjectVisibleFromRunId, async (req, res) => {
|
||||||
const { runId } = req.query;
|
const { runId } = req.query;
|
||||||
|
|
||||||
if (!runId) {
|
if (!runId) {
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ const defineRunCase = require('../../models/runCases');
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
|
const { verifyProjectReporterFromRunId } = require('../../middleware/verifyEditable')(sequelize);
|
||||||
const RunCase = defineRunCase(sequelize, DataTypes);
|
const RunCase = defineRunCase(sequelize, DataTypes);
|
||||||
|
|
||||||
router.post('/', async (req, res) => {
|
router.post('/', verifySignedIn, verifyProjectReporterFromRunId, async (req, res) => {
|
||||||
const runId = req.query.runId;
|
const runId = req.query.runId;
|
||||||
const caseId = req.query.caseId;
|
const caseId = req.query.caseId;
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ const defineRun = require('../../models/runs');
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
|
const { verifyProjectReporterFromRunId } = require('../../middleware/verifyEditable')(sequelize);
|
||||||
const Run = defineRun(sequelize, DataTypes);
|
const Run = defineRun(sequelize, DataTypes);
|
||||||
|
|
||||||
router.delete('/:runId', async (req, res) => {
|
router.delete('/:runId', verifySignedIn, verifyProjectReporterFromRunId, async (req, res) => {
|
||||||
const runId = req.params.runId;
|
const runId = req.params.runId;
|
||||||
try {
|
try {
|
||||||
const testrun = await Run.findByPk(runId);
|
const testrun = await Run.findByPk(runId);
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ const { DataTypes } = require('sequelize');
|
|||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
const Run = defineRun(sequelize, DataTypes);
|
const Run = defineRun(sequelize, DataTypes);
|
||||||
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
|
const { verifyProjectReporterFromRunId } = require('../../middleware/verifyEditable')(sequelize);
|
||||||
|
|
||||||
router.put('/:runId', async (req, res) => {
|
router.put('/:runId', verifySignedIn, verifyProjectReporterFromRunId, async (req, res) => {
|
||||||
const runId = req.params.runId;
|
const runId = req.params.runId;
|
||||||
const updateRun = req.body;
|
const updateRun = req.body;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ const defineRun = require('../../models/runs');
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
|
const { verifyProjectVisibleFromProjectId } = require('../../middleware/verifyVisible')(sequelize);
|
||||||
const Run = defineRun(sequelize, DataTypes);
|
const Run = defineRun(sequelize, DataTypes);
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', verifySignedIn, verifyProjectVisibleFromProjectId, async (req, res) => {
|
||||||
const { projectId } = req.query;
|
const { projectId } = req.query;
|
||||||
|
|
||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ const defineRun = require('../../models/runs');
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
|
const { verifyProjectReporterFromProjectId } = require('../../middleware/verifyEditable')(sequelize);
|
||||||
const Run = defineRun(sequelize, DataTypes);
|
const Run = defineRun(sequelize, DataTypes);
|
||||||
|
|
||||||
router.post('/', async (req, res) => {
|
router.post('/', verifySignedIn, verifyProjectReporterFromProjectId, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { name, configurations, description, state, projectId } = req.body;
|
const projectId = req.query.projectId;
|
||||||
|
const { name, configurations, description, state } = req.body;
|
||||||
if (!name || !projectId) {
|
if (!name || !projectId) {
|
||||||
return res.status(400).json({ error: 'Name and projectId are required' });
|
return res.status(400).json({ error: 'Name and projectId are required' });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ const defineRunCase = require('../../models/runCases');
|
|||||||
const { DataTypes, literal } = require('sequelize');
|
const { DataTypes, literal } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
|
const { verifyProjectVisibleFromRunId } = require('../../middleware/verifyVisible')(sequelize);
|
||||||
const Run = defineRun(sequelize, DataTypes);
|
const Run = defineRun(sequelize, DataTypes);
|
||||||
const RunCase = defineRunCase(sequelize, DataTypes);
|
const RunCase = defineRunCase(sequelize, DataTypes);
|
||||||
|
|
||||||
router.get('/:runId', async (req, res) => {
|
router.get('/:runId', verifySignedIn, verifyProjectVisibleFromRunId, async (req, res) => {
|
||||||
const runId = req.params.runId;
|
const runId = req.params.runId;
|
||||||
|
|
||||||
if (!runId) {
|
if (!runId) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { useState, useEffect, useContext } from 'react';
|
import { useState, useEffect, useContext } from 'react';
|
||||||
import { TokenContext } from '@/utils/TokenProvider';
|
import { TokenContext } from '@/utils/TokenProvider';
|
||||||
import TestCaseTable from './TestCaseTable';
|
import TestCaseTable from './TestCaseTable';
|
||||||
import { fetchCases, createCase, deleteCases } from './caseControl';
|
import { fetchCases, createCase, deleteCases } from '@/utils/caseControl';
|
||||||
import { CaseType, CasesMessages } from '@/types/case';
|
import { CaseType, CasesMessages } from '@/types/case';
|
||||||
import DeleteConfirmDialog from '@/components/DeleteConfirmDialog';
|
import DeleteConfirmDialog from '@/components/DeleteConfirmDialog';
|
||||||
import CaseDialog from './CaseDialog';
|
import CaseDialog from './CaseDialog';
|
||||||
@@ -25,7 +25,7 @@ export default function CasesPane({ projectId, folderId, messages, locale }: Pro
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const data = await fetchCases(context.token.access_token, folderId);
|
const data = await fetchCases(context.token.access_token, Number(folderId));
|
||||||
setCases(data);
|
setCases(data);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error in effect:', error.message);
|
console.error('Error in effect:', error.message);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { priorities, testTypes, templates } from '@/config/selection';
|
|||||||
import CaseStepsEditor from './CaseStepsEditor';
|
import CaseStepsEditor from './CaseStepsEditor';
|
||||||
import CaseAttachmentsEditor from './CaseAttachmentsEditor';
|
import CaseAttachmentsEditor from './CaseAttachmentsEditor';
|
||||||
import { CaseType, AttachmentType, CaseMessages } from '@/types/case';
|
import { CaseType, AttachmentType, CaseMessages } from '@/types/case';
|
||||||
import { fetchCase, updateCase } from '../caseControl';
|
import { fetchCase, updateCase } from '../../../../../../../../../utils/caseControl';
|
||||||
import { fetchCreateStep, fetchDeleteStep } from './stepControl';
|
import { fetchCreateStep, fetchDeleteStep } from './stepControl';
|
||||||
import { fetchCreateAttachments, fetchDownloadAttachment, fetchDeleteAttachment } from './attachmentControl';
|
import { fetchCreateAttachments, fetchDownloadAttachment, fetchDeleteAttachment } from './attachmentControl';
|
||||||
import { TokenContext } from '@/utils/TokenProvider';
|
import { TokenContext } from '@/utils/TokenProvider';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useContext } from 'react';
|
||||||
import { Button } from '@nextui-org/react';
|
import { Button } from '@nextui-org/react';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
import RunsTable from './RunsTable';
|
import RunsTable from './RunsTable';
|
||||||
@@ -7,6 +7,7 @@ import { fetchRuns, createRun, updateRun, deleteRun } from './runsControl';
|
|||||||
import { RunType, RunsMessages } from '@/types/run';
|
import { RunType, RunsMessages } from '@/types/run';
|
||||||
import RunDialog from './RunDialog';
|
import RunDialog from './RunDialog';
|
||||||
import DeleteConfirmDialog from '@/components/DeleteConfirmDialog';
|
import DeleteConfirmDialog from '@/components/DeleteConfirmDialog';
|
||||||
|
import { TokenContext } from '@/utils/TokenProvider';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -15,18 +16,19 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const defaultRun = {
|
const defaultRun = {
|
||||||
id: null,
|
id: 0,
|
||||||
name: 'Untitled Run',
|
name: 'Untitled Run',
|
||||||
configurations: null,
|
configurations: 0,
|
||||||
description: null,
|
description: '',
|
||||||
state: null,
|
state: 0,
|
||||||
projectId: null,
|
projectId: 0,
|
||||||
createdAt: null,
|
createdAt: '',
|
||||||
updatedAt: null,
|
updatedAt: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RunsPage({ projectId, locale, messages }: Props) {
|
export default function RunsPage({ projectId, locale, messages }: Props) {
|
||||||
const [runs, setRuns] = useState([]);
|
const context = useContext(TokenContext);
|
||||||
|
const [runs, setRuns] = useState<RunType[]>([]);
|
||||||
|
|
||||||
// run dialog
|
// run dialog
|
||||||
const [isRunDialogOpen, setIsRunDialogOpen] = useState(false);
|
const [isRunDialogOpen, setIsRunDialogOpen] = useState(false);
|
||||||
@@ -51,8 +53,12 @@ export default function RunsPage({ projectId, locale, messages }: Props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchDataEffect() {
|
async function fetchDataEffect() {
|
||||||
|
if (!context.isSignedIn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await fetchRuns(projectId);
|
const data = await fetchRuns(context.token.access_token, Number(projectId));
|
||||||
setRuns(data);
|
setRuns(data);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error in effect:', error.message);
|
console.error('Error in effect:', error.message);
|
||||||
@@ -60,15 +66,15 @@ export default function RunsPage({ projectId, locale, messages }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchDataEffect();
|
fetchDataEffect();
|
||||||
}, []);
|
}, [context]);
|
||||||
|
|
||||||
const onSubmit = async (name: string, description: string) => {
|
const onSubmit = async (name: string, description: string) => {
|
||||||
if (editingRun && editingRun.createdAt) {
|
if (editingRun && editingRun.createdAt) {
|
||||||
const updatedRun = await updateRun(editingRun);
|
const updatedRun: RunType = await updateRun(context.token.access_token, editingRun);
|
||||||
const updatedRuns = runs.map((run) => (run.id === updatedRun.id ? updatedRun : run));
|
const updatedRuns = runs.map((run) => (run.id === updatedRun.id ? updatedRun : run));
|
||||||
setRuns(updatedRuns);
|
setRuns(updatedRuns);
|
||||||
} else {
|
} else {
|
||||||
const newRun = await createRun(projectId, name, description);
|
const newRun = await createRun(context.token.access_token, Number(projectId), name, description);
|
||||||
setRuns([...runs, newRun]);
|
setRuns([...runs, newRun]);
|
||||||
}
|
}
|
||||||
closeDialog();
|
closeDialog();
|
||||||
@@ -81,7 +87,7 @@ export default function RunsPage({ projectId, locale, messages }: Props) {
|
|||||||
|
|
||||||
const onConfirm = async () => {
|
const onConfirm = async () => {
|
||||||
if (deleteRunId) {
|
if (deleteRunId) {
|
||||||
await deleteRun(deleteRunId);
|
await deleteRun(context.token.access_token, deleteRunId);
|
||||||
setRuns(runs.filter((run) => run.id !== deleteRunId));
|
setRuns(runs.filter((run) => run.id !== deleteRunId));
|
||||||
closeDeleteConfirmDialog();
|
closeDeleteConfirmDialog();
|
||||||
}
|
}
|
||||||
@@ -92,13 +98,26 @@ export default function RunsPage({ projectId, locale, messages }: Props) {
|
|||||||
<div className="w-full p-3 flex items-center justify-between">
|
<div className="w-full p-3 flex items-center justify-between">
|
||||||
<h3 className="font-bold">{messages.runList}</h3>
|
<h3 className="font-bold">{messages.runList}</h3>
|
||||||
<div>
|
<div>
|
||||||
<Button startContent={<Plus size={16} />} size="sm" color="primary" onClick={openDialogForCreate}>
|
<Button
|
||||||
|
startContent={<Plus size={16} />}
|
||||||
|
size="sm"
|
||||||
|
isDisabled={!context.isProjectReporter(Number(projectId))}
|
||||||
|
color="primary"
|
||||||
|
onClick={openDialogForCreate}
|
||||||
|
>
|
||||||
{messages.newRun}
|
{messages.newRun}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RunsTable projectId={projectId} runs={runs} onDeleteRun={onDeleteClick} messages={messages} locale={locale} />
|
<RunsTable
|
||||||
|
projectId={projectId}
|
||||||
|
isDisabled={!context.isProjectReporter(Number(projectId))}
|
||||||
|
runs={runs}
|
||||||
|
onDeleteRun={onDeleteClick}
|
||||||
|
messages={messages}
|
||||||
|
locale={locale}
|
||||||
|
/>
|
||||||
|
|
||||||
<RunDialog
|
<RunDialog
|
||||||
isOpen={isRunDialogOpen}
|
isOpen={isRunDialogOpen}
|
||||||
|
|||||||
@@ -20,13 +20,14 @@ import dayjs from 'dayjs';
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
isDisabled: boolean;
|
||||||
runs: RunType[];
|
runs: RunType[];
|
||||||
onDeleteRun: (runId: number) => void;
|
onDeleteRun: (runId: number) => void;
|
||||||
messages: RunsMessages;
|
messages: RunsMessages;
|
||||||
locale: string;
|
locale: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RunsTable({ projectId, runs, onDeleteRun, messages, locale }: Props) {
|
export default function RunsTable({ projectId, isDisabled, runs, onDeleteRun, messages, locale }: Props) {
|
||||||
const headerColumns = [
|
const headerColumns = [
|
||||||
{ name: messages.id, uid: 'id', sortable: true },
|
{ name: messages.id, uid: 'id', sortable: true },
|
||||||
{ name: messages.name, uid: 'name', sortable: true },
|
{ name: messages.name, uid: 'name', sortable: true },
|
||||||
@@ -83,7 +84,7 @@ export default function RunsTable({ projectId, runs, onDeleteRun, messages, loca
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownTrigger>
|
</DropdownTrigger>
|
||||||
<DropdownMenu aria-label="run actions">
|
<DropdownMenu aria-label="run actions">
|
||||||
<DropdownItem className="text-danger" onClick={() => onDeleteRun(run.id)}>
|
<DropdownItem className="text-danger" isDisabled={isDisabled} onClick={() => onDeleteRun(run.id)}>
|
||||||
{messages.deleteRun}
|
{messages.deleteRun}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useContext } from 'react';
|
||||||
import { useRouter } from '@/src/navigation';
|
import { useRouter } from '@/src/navigation';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -36,7 +36,8 @@ import {
|
|||||||
bulkDeleteRunCases,
|
bulkDeleteRunCases,
|
||||||
} from '../runsControl';
|
} from '../runsControl';
|
||||||
import { fetchFolders } from '../../folders/foldersControl';
|
import { fetchFolders } from '../../folders/foldersControl';
|
||||||
import { fetchCases } from '../../folders/[folderId]/cases/caseControl';
|
import { fetchCases } from '@/utils/caseControl';
|
||||||
|
import { TokenContext } from '@/utils/TokenProvider';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
|
|
||||||
const defaultTestRun = {
|
const defaultTestRun = {
|
||||||
@@ -46,6 +47,8 @@ const defaultTestRun = {
|
|||||||
description: '',
|
description: '',
|
||||||
state: 0,
|
state: 0,
|
||||||
projectId: 0,
|
projectId: 0,
|
||||||
|
createdAt: '',
|
||||||
|
updatedAt: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -56,29 +59,34 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function RunEditor({ projectId, runId, messages, locale }: Props) {
|
export default function RunEditor({ projectId, runId, messages, locale }: Props) {
|
||||||
|
const context = useContext(TokenContext);
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
const [testRun, setTestRun] = useState<RunType>(defaultTestRun);
|
const [testRun, setTestRun] = useState<RunType>(defaultTestRun);
|
||||||
const [folders, setFolders] = useState([]);
|
const [folders, setFolders] = useState<FolderType[]>([]);
|
||||||
const [runCases, setRunCases] = useState<RunCaseType[]>([]);
|
const [runCases, setRunCases] = useState<RunCaseType[]>([]);
|
||||||
const [runStatusCounts, setRunStatusCounts] = useState<RunStatusCountType[]>();
|
const [runStatusCounts, setRunStatusCounts] = useState<RunStatusCountType[]>();
|
||||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||||
const [selectedFolder, setSelectedFolder] = useState<FolderType>({});
|
const [selectedFolder, setSelectedFolder] = useState<FolderType | null>(null);
|
||||||
const [testcases, setTestCases] = useState<CaseType[]>([]);
|
const [testcases, setTestCases] = useState<CaseType[]>([]);
|
||||||
const [isNameInvalid, setIsNameInvalid] = useState<boolean>(false);
|
const [isNameInvalid, setIsNameInvalid] = useState<boolean>(false);
|
||||||
const [isUpdating, setIsUpdating] = useState<boolean>(false);
|
const [isUpdating, setIsUpdating] = useState<boolean>(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const fetchRunAndStatusCount = async () => {
|
const fetchRunAndStatusCount = async () => {
|
||||||
const { run, statusCounts } = await fetchRun(runId);
|
const { run, statusCounts } = await fetchRun(context.token.access_token, Number(runId));
|
||||||
setTestRun(run);
|
setTestRun(run);
|
||||||
setRunStatusCounts(statusCounts);
|
setRunStatusCounts(statusCounts);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchDataEffect() {
|
async function fetchDataEffect() {
|
||||||
|
if (!context.isSignedIn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetchRunAndStatusCount();
|
await fetchRunAndStatusCount();
|
||||||
const foldersData = await fetchFolders(projectId);
|
const foldersData = await fetchFolders(context.token.access_token, projectId);
|
||||||
setFolders(foldersData);
|
setFolders(foldersData);
|
||||||
setSelectedFolder(foldersData[0]);
|
setSelectedFolder(foldersData[0]);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -87,16 +95,16 @@ export default function RunEditor({ projectId, runId, messages, locale }: Props)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchDataEffect();
|
fetchDataEffect();
|
||||||
}, []);
|
}, [context]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchCasesData() {
|
async function fetchCasesData() {
|
||||||
if (selectedFolder && selectedFolder.id) {
|
if (selectedFolder && selectedFolder.id) {
|
||||||
try {
|
try {
|
||||||
const latestRunCases = await fetchRunCases(runId);
|
const latestRunCases: RunCaseType[] = await fetchRunCases(context.token.access_token, Number(runId));
|
||||||
setRunCases(latestRunCases);
|
setRunCases(latestRunCases);
|
||||||
|
|
||||||
const testCasesData = await fetchCases(selectedFolder.id);
|
const testCasesData: CaseType[] = await fetchCases(context.token.access_token, selectedFolder.id);
|
||||||
// Check if each testCase has an association with testRun
|
// Check if each testCase has an association with testRun
|
||||||
// and add "isIncluded" property
|
// and add "isIncluded" property
|
||||||
const updatedTestCasesData = testCasesData.map((testCase) => {
|
const updatedTestCasesData = testCasesData.map((testCase) => {
|
||||||
@@ -122,7 +130,7 @@ export default function RunEditor({ projectId, runId, messages, locale }: Props)
|
|||||||
}, [selectedFolder]);
|
}, [selectedFolder]);
|
||||||
|
|
||||||
const handleChangeStatus = async (changeCaseId: number, status: number) => {
|
const handleChangeStatus = async (changeCaseId: number, status: number) => {
|
||||||
await updateRunCase(runId, changeCaseId, status);
|
await updateRunCase(context.token.access_token, Number(runId), changeCaseId, status);
|
||||||
setTestCases((prevTestCases) => {
|
setTestCases((prevTestCases) => {
|
||||||
return prevTestCases.map((testCase) => {
|
return prevTestCases.map((testCase) => {
|
||||||
if (testCase.id === changeCaseId) {
|
if (testCase.id === changeCaseId) {
|
||||||
@@ -135,12 +143,12 @@ export default function RunEditor({ projectId, runId, messages, locale }: Props)
|
|||||||
|
|
||||||
const handleIncludeExcludeCase = async (isInclude: boolean, clickedTestCaseId: number) => {
|
const handleIncludeExcludeCase = async (isInclude: boolean, clickedTestCaseId: number) => {
|
||||||
if (isInclude) {
|
if (isInclude) {
|
||||||
const createdRunCase = await createRunCase(runId, clickedTestCaseId);
|
const createdRunCase = await createRunCase(context.token.access_token, Number(runId), clickedTestCaseId);
|
||||||
setRunCases((prevRunCases) => {
|
setRunCases((prevRunCases) => {
|
||||||
return [...prevRunCases, createdRunCase];
|
return [...prevRunCases, createdRunCase];
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await deleteRunCase(runId, clickedTestCaseId);
|
await deleteRunCase(context.token.access_token, Number(runId), clickedTestCaseId);
|
||||||
setRunCases((prevRunCases) => {
|
setRunCases((prevRunCases) => {
|
||||||
return prevRunCases.filter((runCase) => runCase.caseId !== clickedTestCaseId);
|
return prevRunCases.filter((runCase) => runCase.caseId !== clickedTestCaseId);
|
||||||
});
|
});
|
||||||
@@ -165,14 +173,14 @@ export default function RunEditor({ projectId, runId, messages, locale }: Props)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runCaseInfo: RunCaseInfoType[] = keys.map((caseId) => ({
|
const runCaseInfo: RunCaseInfoType[] = keys.map((caseId) => ({
|
||||||
runId: runId,
|
runId: Number(runId),
|
||||||
caseId: caseId,
|
caseId: caseId,
|
||||||
}));
|
}));
|
||||||
if (isInclude) {
|
if (isInclude) {
|
||||||
const createdRunCases = await bulkCreateRunCases(runCaseInfo);
|
const createdRunCases = await bulkCreateRunCases(context.token.access_token, Number(runId), runCaseInfo);
|
||||||
setRunCases((prevRunCases) => [...prevRunCases, ...createdRunCases]);
|
setRunCases((prevRunCases) => [...prevRunCases, ...createdRunCases]);
|
||||||
} else {
|
} else {
|
||||||
await bulkDeleteRunCases(runCaseInfo);
|
await bulkDeleteRunCases(context.token.access_token, Number(runId), runCaseInfo);
|
||||||
setRunCases((prevRunCases) => {
|
setRunCases((prevRunCases) => {
|
||||||
return prevRunCases.filter((runCase) => {
|
return prevRunCases.filter((runCase) => {
|
||||||
return !runCaseInfo.some((info) => info.caseId === runCase.caseId);
|
return !runCaseInfo.some((info) => info.caseId === runCase.caseId);
|
||||||
@@ -217,7 +225,7 @@ export default function RunEditor({ projectId, runId, messages, locale }: Props)
|
|||||||
isLoading={isUpdating}
|
isLoading={isUpdating}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
setIsUpdating(true);
|
setIsUpdating(true);
|
||||||
await updateRun(testRun);
|
await updateRun(context.token.access_token, testRun);
|
||||||
setIsUpdating(false);
|
setIsUpdating(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const Chart = dynamic(() => import('react-apexcharts'), { ssr: false });
|
|||||||
type Props = {
|
type Props = {
|
||||||
statusCounts: RunStatusCountType[];
|
statusCounts: RunStatusCountType[];
|
||||||
messages: RunMessages;
|
messages: RunMessages;
|
||||||
theme: string;
|
theme: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RunProgressDounut({ statusCounts, messages, theme }: Props) {
|
export default function RunProgressDounut({ statusCounts, messages, theme }: Props) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Config from '@/config/config';
|
|||||||
const apiServer = Config.apiServer;
|
const apiServer = Config.apiServer;
|
||||||
import { RunType, RunCaseInfoType } from '@/types/run';
|
import { RunType, RunCaseInfoType } from '@/types/run';
|
||||||
|
|
||||||
async function fetchRun(runId: string) {
|
async function fetchRun(jwt: string, runId: number) {
|
||||||
const url = `${apiServer}/runs/${runId}`;
|
const url = `${apiServer}/runs/${runId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -10,6 +10,7 @@ async function fetchRun(runId: string) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ async function fetchRun(runId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchRuns(projectId: string) {
|
async function fetchRuns(jwt: string, projectId: number) {
|
||||||
const url = `${apiServer}/runs?projectId=${projectId}`;
|
const url = `${apiServer}/runs?projectId=${projectId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -32,6 +33,7 @@ async function fetchRuns(projectId: string) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -46,24 +48,24 @@ async function fetchRuns(projectId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createRun(projectId: string, name: string, description: string) {
|
async function createRun(jwt: string, projectId: number, name: string, description: string) {
|
||||||
const newTestRun = {
|
const newTestRun = {
|
||||||
name,
|
name,
|
||||||
configurations: 0,
|
configurations: 0,
|
||||||
description,
|
description,
|
||||||
state: 0,
|
state: 0,
|
||||||
projectId: projectId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(newTestRun),
|
body: JSON.stringify(newTestRun),
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = `${apiServer}/runs`;
|
const url = `${apiServer}/runs?projectId=${projectId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, fetchOptions);
|
const response = await fetch(url, fetchOptions);
|
||||||
@@ -78,11 +80,12 @@ async function createRun(projectId: string, name: string, description: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateRun(updateTestRun: RunType) {
|
async function updateRun(jwt: string, updateTestRun: RunType) {
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(updateTestRun),
|
body: JSON.stringify(updateTestRun),
|
||||||
};
|
};
|
||||||
@@ -102,11 +105,12 @@ async function updateRun(updateTestRun: RunType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteRun(runId: number) {
|
async function deleteRun(jwt: string, runId: number) {
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,7 +127,7 @@ async function deleteRun(runId: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchRunCases(runId: string) {
|
async function fetchRunCases(jwt: string, runId: number) {
|
||||||
const url = `${apiServer}/runcases?runId=${runId}`;
|
const url = `${apiServer}/runcases?runId=${runId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -131,6 +135,7 @@ async function fetchRunCases(runId: string) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -145,11 +150,12 @@ async function fetchRunCases(runId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createRunCase(runId: string, caseId: number) {
|
async function createRunCase(jwt: string, runId: number, caseId: number) {
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -168,11 +174,12 @@ async function createRunCase(runId: string, caseId: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateRunCase(runId: string, caseId: number, status: number) {
|
async function updateRunCase(jwt: string, runId: number, caseId: number, status: number) {
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -191,16 +198,17 @@ async function updateRunCase(runId: string, caseId: number, status: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bulkCreateRunCases(runCaseInfo: RunCaseInfoType[]) {
|
async function bulkCreateRunCases(jwt: string, runId: number, runCaseInfo: RunCaseInfoType[]) {
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(runCaseInfo),
|
body: JSON.stringify(runCaseInfo),
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = `${apiServer}/runcases/bulknew`;
|
const url = `${apiServer}/runcases/bulknew?runId=${runId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, fetchOptions);
|
const response = await fetch(url, fetchOptions);
|
||||||
@@ -215,11 +223,12 @@ async function bulkCreateRunCases(runCaseInfo: RunCaseInfoType[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteRunCase(runId: string, caseId: number) {
|
async function deleteRunCase(jwt: string, runId: number, caseId: number) {
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -236,16 +245,17 @@ async function deleteRunCase(runId: string, caseId: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bulkDeleteRunCases(runCaseInfo: RunCaseInfoType[]) {
|
async function bulkDeleteRunCases(jwt: string, runId: number, runCaseInfo: RunCaseInfoType[]) {
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(runCaseInfo),
|
body: JSON.stringify(runCaseInfo),
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = `${apiServer}/runcases/bulkdelete`;
|
const url = `${apiServer}/runcases/bulkdelete?runId=${runId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, fetchOptions);
|
const response = await fetch(url, fetchOptions);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export type TokenContextType = {
|
|||||||
isAdmin: () => boolean;
|
isAdmin: () => boolean;
|
||||||
isProjectManager: (projectId: number) => boolean;
|
isProjectManager: (projectId: number) => boolean;
|
||||||
isProjectDeveloper: (projectId: number) => boolean;
|
isProjectDeveloper: (projectId: number) => boolean;
|
||||||
|
isProjectReporter: (projectId: number) => boolean;
|
||||||
setToken: (token: TokenType) => void;
|
setToken: (token: TokenType) => void;
|
||||||
storeTokenToLocalStorage: (token: TokenType) => void;
|
storeTokenToLocalStorage: (token: TokenType) => void;
|
||||||
removeTokenFromLocalStorage: () => void;
|
removeTokenFromLocalStorage: () => void;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
isAdmin as tokenIsAdmin,
|
isAdmin as tokenIsAdmin,
|
||||||
isProjectManager as tokenIsProjectManager,
|
isProjectManager as tokenIsProjectManager,
|
||||||
isProjectDeveloper as tokenIsProjectDeveloper,
|
isProjectDeveloper as tokenIsProjectDeveloper,
|
||||||
|
isProjectReporter as tokenIsProjectReporter,
|
||||||
checkSignInPage as tokenCheckSignInPage,
|
checkSignInPage as tokenCheckSignInPage,
|
||||||
fetchMyRoles,
|
fetchMyRoles,
|
||||||
} from './token';
|
} from './token';
|
||||||
@@ -70,6 +71,10 @@ const TokenProvider = ({ toastMessages, locale, children }: TokenProps) => {
|
|||||||
return tokenIsProjectDeveloper(projectRoles, projectId);
|
return tokenIsProjectDeveloper(projectRoles, projectId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isProjectReporter = (projectId: number) => {
|
||||||
|
return tokenIsProjectReporter(projectRoles, projectId);
|
||||||
|
};
|
||||||
|
|
||||||
const tokenContext = {
|
const tokenContext = {
|
||||||
token,
|
token,
|
||||||
projectRoles,
|
projectRoles,
|
||||||
@@ -77,6 +82,7 @@ const TokenProvider = ({ toastMessages, locale, children }: TokenProps) => {
|
|||||||
isAdmin,
|
isAdmin,
|
||||||
isProjectManager,
|
isProjectManager,
|
||||||
isProjectDeveloper,
|
isProjectDeveloper,
|
||||||
|
isProjectReporter,
|
||||||
setToken,
|
setToken,
|
||||||
storeTokenToLocalStorage,
|
storeTokenToLocalStorage,
|
||||||
removeTokenFromLocalStorage,
|
removeTokenFromLocalStorage,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ async function fetchCase(jwt: string, caseId: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchCases(jwt: string, folderId: string) {
|
async function fetchCases(jwt: string, folderId: number) {
|
||||||
const url = `${apiServer}/cases?folderId=${folderId}`;
|
const url = `${apiServer}/cases?folderId=${folderId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -112,6 +112,33 @@ function isProjectDeveloper(projectRoles: ProjectRoleType[], projectId: number)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isProjectReporter(projectRoles: ProjectRoleType[], projectId: number) {
|
||||||
|
if (!projectRoles) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const found = projectRoles.find((role) => {
|
||||||
|
return role.projectId === projectId;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found.isOwner === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const managerRoleIndex = memberRoles.findIndex((entry) => entry.uid === 'manager');
|
||||||
|
const developerRoleIndex = memberRoles.findIndex((entry) => entry.uid === 'developer');
|
||||||
|
const reporterRoleIndex = memberRoles.findIndex((entry) => entry.uid === 'reporter');
|
||||||
|
if (found.role === managerRoleIndex || found.role === developerRoleIndex || found.role === reporterRoleIndex) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// private paths are '/account', '/admin', '/projects/*'
|
// private paths are '/account', '/admin', '/projects/*'
|
||||||
const isPrivatePath = (pathname: string) => {
|
const isPrivatePath = (pathname: string) => {
|
||||||
return /^\/account(\/)?$/.test(pathname) || /^\/admin(\/.*)?$/.test(pathname) || /^\/projects(\/.*)?$/.test(pathname);
|
return /^\/account(\/)?$/.test(pathname) || /^\/admin(\/.*)?$/.test(pathname) || /^\/projects(\/.*)?$/.test(pathname);
|
||||||
@@ -142,4 +169,13 @@ function checkSignInPage(token: TokenType, pathname: string) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { isSignedIn, isAdmin, isProjectManager, isProjectDeveloper, isPrivatePath, checkSignInPage, fetchMyRoles };
|
export {
|
||||||
|
isSignedIn,
|
||||||
|
isAdmin,
|
||||||
|
isProjectManager,
|
||||||
|
isProjectDeveloper,
|
||||||
|
isProjectReporter,
|
||||||
|
isPrivatePath,
|
||||||
|
checkSignInPage,
|
||||||
|
fetchMyRoles,
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user