Merge pull request #1714 from automatisch/AUT-693
refactor: rewrite get executions using useExecutions with RQ
This commit is contained in:
@@ -1,70 +0,0 @@
|
||||
import { raw } from 'objection';
|
||||
import { DateTime } from 'luxon';
|
||||
import Execution from '../../models/execution.js';
|
||||
import paginate from '../../helpers/pagination.js';
|
||||
|
||||
const getExecutions = async (_parent, params, context) => {
|
||||
const conditions = context.currentUser.can('read', 'Execution');
|
||||
|
||||
const filters = params.filters;
|
||||
|
||||
const userExecutions = context.currentUser.$relatedQuery('executions');
|
||||
const allExecutions = Execution.query();
|
||||
const executionBaseQuery = conditions.isCreator
|
||||
? userExecutions
|
||||
: allExecutions;
|
||||
|
||||
const selectStatusStatement = `
|
||||
case
|
||||
when count(*) filter (where execution_steps.status = 'failure') > 0
|
||||
then 'failure'
|
||||
else 'success'
|
||||
end
|
||||
as status
|
||||
`;
|
||||
|
||||
const executions = executionBaseQuery
|
||||
.clone()
|
||||
.joinRelated('executionSteps as execution_steps')
|
||||
.select('executions.*', raw(selectStatusStatement))
|
||||
.groupBy('executions.id')
|
||||
.orderBy('created_at', 'desc');
|
||||
|
||||
const computedExecutions = Execution.query()
|
||||
.with('executions', executions)
|
||||
.withSoftDeleted()
|
||||
.withGraphFetched({
|
||||
flow: {
|
||||
steps: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (filters?.flowId) {
|
||||
computedExecutions.where('executions.flow_id', filters.flowId);
|
||||
}
|
||||
|
||||
if (filters?.status) {
|
||||
computedExecutions.where('executions.status', filters.status);
|
||||
}
|
||||
|
||||
if (filters?.createdAt) {
|
||||
const createdAtFilter = filters.createdAt;
|
||||
if (createdAtFilter.from) {
|
||||
const isoFromDateTime = DateTime.fromMillis(
|
||||
parseInt(createdAtFilter.from, 10)
|
||||
).toISO();
|
||||
computedExecutions.where('executions.created_at', '>=', isoFromDateTime);
|
||||
}
|
||||
|
||||
if (createdAtFilter.to) {
|
||||
const isoToDateTime = DateTime.fromMillis(
|
||||
parseInt(createdAtFilter.to, 10)
|
||||
).toISO();
|
||||
computedExecutions.where('executions.created_at', '<=', isoToDateTime);
|
||||
}
|
||||
}
|
||||
|
||||
return paginate(computedExecutions, params.limit, params.offset);
|
||||
};
|
||||
|
||||
export default getExecutions;
|
@@ -1,472 +0,0 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import request from 'supertest';
|
||||
import app from '../../app';
|
||||
import appConfig from '../../config/app';
|
||||
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
||||
import { createRole } from '../../../test/factories/role';
|
||||
import { createPermission } from '../../../test/factories/permission';
|
||||
import { createUser } from '../../../test/factories/user';
|
||||
import { createFlow } from '../../../test/factories/flow';
|
||||
import { createStep } from '../../../test/factories/step';
|
||||
import { createExecution } from '../../../test/factories/execution';
|
||||
import { createExecutionStep } from '../../../test/factories/execution-step';
|
||||
|
||||
describe('graphQL getExecutions query', () => {
|
||||
const query = `
|
||||
query {
|
||||
getExecutions(limit: 10, offset: 0) {
|
||||
pageInfo {
|
||||
currentPage
|
||||
totalPages
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
testRun
|
||||
createdAt
|
||||
updatedAt
|
||||
status
|
||||
flow {
|
||||
id
|
||||
name
|
||||
active
|
||||
steps {
|
||||
iconUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
describe('and without correct permissions', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const userWithoutPermissions = await createUser();
|
||||
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', token)
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.errors).toBeDefined();
|
||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and with correct permission', () => {
|
||||
let role,
|
||||
currentUser,
|
||||
anotherUser,
|
||||
token,
|
||||
flowOne,
|
||||
stepOneForFlowOne,
|
||||
stepTwoForFlowOne,
|
||||
executionOne,
|
||||
flowTwo,
|
||||
stepOneForFlowTwo,
|
||||
stepTwoForFlowTwo,
|
||||
executionTwo,
|
||||
flowThree,
|
||||
stepOneForFlowThree,
|
||||
stepTwoForFlowThree,
|
||||
executionThree,
|
||||
expectedResponseForExecutionOne,
|
||||
expectedResponseForExecutionTwo,
|
||||
expectedResponseForExecutionThree;
|
||||
|
||||
beforeEach(async () => {
|
||||
role = await createRole({
|
||||
key: 'sample',
|
||||
name: 'sample',
|
||||
});
|
||||
|
||||
currentUser = await createUser({
|
||||
roleId: role.id,
|
||||
fullName: 'Current User',
|
||||
});
|
||||
|
||||
anotherUser = await createUser();
|
||||
|
||||
token = createAuthTokenByUserId(currentUser.id);
|
||||
|
||||
flowOne = await createFlow({
|
||||
userId: currentUser.id,
|
||||
});
|
||||
|
||||
stepOneForFlowOne = await createStep({
|
||||
flowId: flowOne.id,
|
||||
});
|
||||
|
||||
stepTwoForFlowOne = await createStep({
|
||||
flowId: flowOne.id,
|
||||
});
|
||||
|
||||
executionOne = await createExecution({
|
||||
flowId: flowOne.id,
|
||||
});
|
||||
|
||||
await createExecutionStep({
|
||||
executionId: executionOne.id,
|
||||
stepId: stepOneForFlowOne.id,
|
||||
status: 'success',
|
||||
});
|
||||
|
||||
await createExecutionStep({
|
||||
executionId: executionOne.id,
|
||||
stepId: stepTwoForFlowOne.id,
|
||||
status: 'success',
|
||||
});
|
||||
|
||||
flowTwo = await createFlow({
|
||||
userId: currentUser.id,
|
||||
});
|
||||
|
||||
stepOneForFlowTwo = await createStep({
|
||||
flowId: flowTwo.id,
|
||||
});
|
||||
|
||||
stepTwoForFlowTwo = await createStep({
|
||||
flowId: flowTwo.id,
|
||||
});
|
||||
|
||||
executionTwo = await createExecution({
|
||||
flowId: flowTwo.id,
|
||||
});
|
||||
|
||||
await createExecutionStep({
|
||||
executionId: executionTwo.id,
|
||||
stepId: stepOneForFlowTwo.id,
|
||||
status: 'success',
|
||||
});
|
||||
|
||||
await createExecutionStep({
|
||||
executionId: executionTwo.id,
|
||||
stepId: stepTwoForFlowTwo.id,
|
||||
status: 'failure',
|
||||
});
|
||||
|
||||
flowThree = await createFlow({
|
||||
userId: anotherUser.id,
|
||||
});
|
||||
|
||||
stepOneForFlowThree = await createStep({
|
||||
flowId: flowThree.id,
|
||||
});
|
||||
|
||||
stepTwoForFlowThree = await createStep({
|
||||
flowId: flowThree.id,
|
||||
});
|
||||
|
||||
executionThree = await createExecution({
|
||||
flowId: flowThree.id,
|
||||
});
|
||||
|
||||
await createExecutionStep({
|
||||
executionId: executionThree.id,
|
||||
stepId: stepOneForFlowThree.id,
|
||||
status: 'success',
|
||||
});
|
||||
|
||||
await createExecutionStep({
|
||||
executionId: executionThree.id,
|
||||
stepId: stepTwoForFlowThree.id,
|
||||
status: 'failure',
|
||||
});
|
||||
|
||||
expectedResponseForExecutionOne = {
|
||||
node: {
|
||||
createdAt: executionOne.createdAt.getTime().toString(),
|
||||
flow: {
|
||||
active: flowOne.active,
|
||||
id: flowOne.id,
|
||||
name: flowOne.name,
|
||||
steps: [
|
||||
{
|
||||
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowOne.appKey}/assets/favicon.svg`,
|
||||
},
|
||||
{
|
||||
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowOne.appKey}/assets/favicon.svg`,
|
||||
},
|
||||
],
|
||||
},
|
||||
id: executionOne.id,
|
||||
status: 'success',
|
||||
testRun: executionOne.testRun,
|
||||
updatedAt: executionOne.updatedAt.getTime().toString(),
|
||||
},
|
||||
};
|
||||
|
||||
expectedResponseForExecutionTwo = {
|
||||
node: {
|
||||
createdAt: executionTwo.createdAt.getTime().toString(),
|
||||
flow: {
|
||||
active: flowTwo.active,
|
||||
id: flowTwo.id,
|
||||
name: flowTwo.name,
|
||||
steps: [
|
||||
{
|
||||
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
|
||||
},
|
||||
{
|
||||
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
|
||||
},
|
||||
],
|
||||
},
|
||||
id: executionTwo.id,
|
||||
status: 'failure',
|
||||
testRun: executionTwo.testRun,
|
||||
updatedAt: executionTwo.updatedAt.getTime().toString(),
|
||||
},
|
||||
};
|
||||
|
||||
expectedResponseForExecutionThree = {
|
||||
node: {
|
||||
createdAt: executionThree.createdAt.getTime().toString(),
|
||||
flow: {
|
||||
active: flowThree.active,
|
||||
id: flowThree.id,
|
||||
name: flowThree.name,
|
||||
steps: [
|
||||
{
|
||||
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowThree.appKey}/assets/favicon.svg`,
|
||||
},
|
||||
{
|
||||
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowThree.appKey}/assets/favicon.svg`,
|
||||
},
|
||||
],
|
||||
},
|
||||
id: executionThree.id,
|
||||
status: 'failure',
|
||||
testRun: executionThree.testRun,
|
||||
updatedAt: executionThree.updatedAt.getTime().toString(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('and with isCreator condition', () => {
|
||||
beforeEach(async () => {
|
||||
await createPermission({
|
||||
action: 'read',
|
||||
subject: 'Execution',
|
||||
roleId: role.id,
|
||||
conditions: ['isCreator'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return executions data of the current user', async () => {
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', token)
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
const expectedResponsePayload = {
|
||||
data: {
|
||||
getExecutions: {
|
||||
edges: [
|
||||
expectedResponseForExecutionTwo,
|
||||
expectedResponseForExecutionOne,
|
||||
],
|
||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(response.body).toEqual(expectedResponsePayload);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and without isCreator condition', () => {
|
||||
beforeEach(async () => {
|
||||
await createPermission({
|
||||
action: 'read',
|
||||
subject: 'Execution',
|
||||
roleId: role.id,
|
||||
conditions: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return executions data of all users', async () => {
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', token)
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
const expectedResponsePayload = {
|
||||
data: {
|
||||
getExecutions: {
|
||||
edges: [
|
||||
expectedResponseForExecutionThree,
|
||||
expectedResponseForExecutionTwo,
|
||||
expectedResponseForExecutionOne,
|
||||
],
|
||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(response.body).toEqual(expectedResponsePayload);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and with filters', () => {
|
||||
beforeEach(async () => {
|
||||
await createPermission({
|
||||
action: 'read',
|
||||
subject: 'Execution',
|
||||
roleId: role.id,
|
||||
conditions: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return executions data for the specified flow', async () => {
|
||||
const query = `
|
||||
query {
|
||||
getExecutions(limit: 10, offset: 0, filters: { flowId: "${flowOne.id}" }) {
|
||||
pageInfo {
|
||||
currentPage
|
||||
totalPages
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
testRun
|
||||
createdAt
|
||||
updatedAt
|
||||
status
|
||||
flow {
|
||||
id
|
||||
name
|
||||
active
|
||||
steps {
|
||||
iconUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', token)
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
const expectedResponsePayload = {
|
||||
data: {
|
||||
getExecutions: {
|
||||
edges: [expectedResponseForExecutionOne],
|
||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(response.body).toEqual(expectedResponsePayload);
|
||||
});
|
||||
|
||||
it('should return only executions data with success status', async () => {
|
||||
const query = `
|
||||
query {
|
||||
getExecutions(limit: 10, offset: 0, filters: { status: "success" }) {
|
||||
pageInfo {
|
||||
currentPage
|
||||
totalPages
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
testRun
|
||||
createdAt
|
||||
updatedAt
|
||||
status
|
||||
flow {
|
||||
id
|
||||
name
|
||||
active
|
||||
steps {
|
||||
iconUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', token)
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
const expectedResponsePayload = {
|
||||
data: {
|
||||
getExecutions: {
|
||||
edges: [expectedResponseForExecutionOne],
|
||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(response.body).toEqual(expectedResponsePayload);
|
||||
});
|
||||
|
||||
it('should return only executions data within date range', async () => {
|
||||
const createdAtFrom = executionOne.createdAt.getTime().toString();
|
||||
|
||||
const createdAtTo = executionOne.createdAt.getTime().toString();
|
||||
|
||||
const query = `
|
||||
query {
|
||||
getExecutions(limit: 10, offset: 0, filters: { createdAt: { from: "${createdAtFrom}", to: "${createdAtTo}" }}) {
|
||||
pageInfo {
|
||||
currentPage
|
||||
totalPages
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
testRun
|
||||
createdAt
|
||||
updatedAt
|
||||
status
|
||||
flow {
|
||||
id
|
||||
name
|
||||
active
|
||||
steps {
|
||||
iconUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', token)
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
const expectedResponsePayload = {
|
||||
data: {
|
||||
getExecutions: {
|
||||
edges: [expectedResponseForExecutionOne],
|
||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(response.body).toEqual(expectedResponsePayload);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -7,7 +7,6 @@ import getConnectedApps from './queries/get-connected-apps.js';
|
||||
import getCurrentUser from './queries/get-current-user.js';
|
||||
import getDynamicData from './queries/get-dynamic-data.js';
|
||||
import getDynamicFields from './queries/get-dynamic-fields.js';
|
||||
import getExecutions from './queries/get-executions.js';
|
||||
import getFlow from './queries/get-flow.js';
|
||||
import getFlows from './queries/get-flows.js';
|
||||
import getInvoices from './queries/get-invoices.ee.js';
|
||||
@@ -38,7 +37,6 @@ const queryResolvers = {
|
||||
getCurrentUser,
|
||||
getDynamicData,
|
||||
getDynamicFields,
|
||||
getExecutions,
|
||||
getFlow,
|
||||
getFlows,
|
||||
getInvoices,
|
||||
|
@@ -13,11 +13,6 @@ type Query {
|
||||
name: String
|
||||
): FlowConnection
|
||||
getStepWithTestExecutions(stepId: String!): [Step]
|
||||
getExecutions(
|
||||
limit: Int!
|
||||
offset: Int!
|
||||
filters: ExecutionFiltersInput
|
||||
): ExecutionConnection
|
||||
getDynamicData(
|
||||
stepId: String!
|
||||
key: String!
|
||||
@@ -301,15 +296,6 @@ type Flow {
|
||||
status: FlowStatus
|
||||
}
|
||||
|
||||
type Execution {
|
||||
id: String
|
||||
testRun: Boolean
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
status: String
|
||||
flow: Flow
|
||||
}
|
||||
|
||||
type SamlAuthProvider {
|
||||
id: String
|
||||
name: String
|
||||
@@ -609,19 +595,10 @@ type PageInfo {
|
||||
totalPages: Int!
|
||||
}
|
||||
|
||||
type ExecutionEdge {
|
||||
node: Execution
|
||||
}
|
||||
|
||||
type ExecutionStepEdge {
|
||||
node: ExecutionStep
|
||||
}
|
||||
|
||||
type ExecutionConnection {
|
||||
edges: [ExecutionEdge]
|
||||
pageInfo: PageInfo
|
||||
}
|
||||
|
||||
type ExecutionStepConnection {
|
||||
edges: [ExecutionStepEdge]
|
||||
pageInfo: PageInfo
|
||||
@@ -781,17 +758,6 @@ type Notification {
|
||||
description: String
|
||||
}
|
||||
|
||||
input ExecutionCreatedAtFilterInput {
|
||||
from: String
|
||||
to: String
|
||||
}
|
||||
|
||||
input ExecutionFiltersInput {
|
||||
flowId: String
|
||||
createdAt: ExecutionCreatedAtFilterInput
|
||||
status: String
|
||||
}
|
||||
|
||||
schema {
|
||||
query: Query
|
||||
mutation: Mutation
|
||||
|
@@ -1,28 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
export const GET_EXECUTIONS = gql`
|
||||
query GetExecutions($limit: Int!, $offset: Int!) {
|
||||
getExecutions(limit: $limit, offset: $offset) {
|
||||
pageInfo {
|
||||
currentPage
|
||||
totalPages
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
testRun
|
||||
createdAt
|
||||
updatedAt
|
||||
status
|
||||
flow {
|
||||
id
|
||||
name
|
||||
active
|
||||
steps {
|
||||
iconUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
22
packages/web/src/hooks/useExecutions.js
Normal file
22
packages/web/src/hooks/useExecutions.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import api from 'helpers/api';
|
||||
|
||||
export default function useExecutions({ page }, { refetchInterval } = {}) {
|
||||
const query = useQuery({
|
||||
queryKey: ['executions', page],
|
||||
queryFn: async ({ signal }) => {
|
||||
const { data } = await api.get(`/v1/executions`, {
|
||||
params: {
|
||||
page,
|
||||
},
|
||||
signal,
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
refetchInterval,
|
||||
});
|
||||
|
||||
return query;
|
||||
}
|
@@ -1,39 +1,33 @@
|
||||
import * as React from 'react';
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import Box from '@mui/material/Box';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Pagination from '@mui/material/Pagination';
|
||||
import PaginationItem from '@mui/material/PaginationItem';
|
||||
|
||||
import NoResultFound from 'components/NoResultFound';
|
||||
import ExecutionRow from 'components/ExecutionRow';
|
||||
import Container from 'components/Container';
|
||||
import PageTitle from 'components/PageTitle';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import { GET_EXECUTIONS } from 'graphql/queries/get-executions';
|
||||
const EXECUTION_PER_PAGE = 10;
|
||||
const getLimitAndOffset = (page) => ({
|
||||
limit: EXECUTION_PER_PAGE,
|
||||
offset: (page - 1) * EXECUTION_PER_PAGE,
|
||||
});
|
||||
import useExecutions from 'hooks/useExecutions';
|
||||
|
||||
export default function Executions() {
|
||||
const formatMessage = useFormatMessage();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const page = parseInt(searchParams.get('page') || '', 10) || 1;
|
||||
const { data, refetch, loading } = useQuery(GET_EXECUTIONS, {
|
||||
variables: getLimitAndOffset(page),
|
||||
fetchPolicy: 'cache-and-network',
|
||||
pollInterval: 5000,
|
||||
});
|
||||
const getExecutions = data?.getExecutions || {};
|
||||
const { pageInfo, edges } = getExecutions;
|
||||
React.useEffect(() => {
|
||||
refetch(getLimitAndOffset(page));
|
||||
}, [refetch, page]);
|
||||
const executions = edges?.map(({ node }) => node);
|
||||
|
||||
const { data, isLoading: isExecutionsLoading } = useExecutions(
|
||||
{ page: page },
|
||||
{ refetchInterval: 5000 },
|
||||
);
|
||||
|
||||
const { data: executions, meta: pageInfo } = data || {};
|
||||
|
||||
const hasExecutions = executions?.length;
|
||||
|
||||
return (
|
||||
<Box sx={{ py: 3 }}>
|
||||
<Container>
|
||||
@@ -52,18 +46,18 @@ export default function Executions() {
|
||||
|
||||
<Divider sx={{ mt: [2, 0], mb: 2 }} />
|
||||
|
||||
{loading && (
|
||||
{isExecutionsLoading && (
|
||||
<CircularProgress
|
||||
data-test="executions-loader"
|
||||
sx={{ display: 'block', margin: '20px auto' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!loading && !hasExecutions && (
|
||||
{!isExecutionsLoading && !hasExecutions && (
|
||||
<NoResultFound text={formatMessage('executions.noExecutions')} />
|
||||
)}
|
||||
|
||||
{!loading &&
|
||||
{!isExecutionsLoading &&
|
||||
executions?.map((execution) => (
|
||||
<ExecutionRow key={execution.id} execution={execution} />
|
||||
))}
|
||||
|
Reference in New Issue
Block a user