Skip to content

Commit

Permalink
Fix for UI issues in workflow page (#58)
Browse files Browse the repository at this point in the history
* Fix for UI issues in workflow page

* Addressed review comments

---------

Co-authored-by: Hari John Kuriakose <[email protected]>
Co-authored-by: Athul <[email protected]>
  • Loading branch information
3 people authored Mar 5, 2024
1 parent ffa1674 commit 22a0c58
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 58 deletions.
6 changes: 6 additions & 0 deletions backend/workflow_manager/workflow/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
{"get": "clear_file_marker"}
)
workflow_schema = WorkflowViewSet.as_view({"get": "get_schema"})
can_update = WorkflowViewSet.as_view({"get": "can_update"})
urlpatterns = format_suffix_patterns(
[
path("", workflow_list, name="workflow-list"),
Expand All @@ -41,6 +42,11 @@
workflow_clear_file_marker,
name="clear-file-marker",
),
path(
"<uuid:pk>/can-update/",
can_update,
name="can-update",
),
path("execute/", workflow_execute, name="execute-workflow"),
path(
"active/<uuid:pk>/",
Expand Down
12 changes: 9 additions & 3 deletions backend/workflow_manager/workflow/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
from workflow_manager.workflow.enums import SchemaEntity, SchemaType
from workflow_manager.workflow.exceptions import (
InvalidRequest,
MissingEnvException,
WorkflowDoesNotExistError,
WorkflowExecutionError,
WorkflowGenerationError,
WorkflowRegenerationError,
MissingEnvException,
)
from workflow_manager.workflow.generator import WorkflowGenerator
from workflow_manager.workflow.models.workflow import Workflow
Expand Down Expand Up @@ -248,9 +248,10 @@ def execute(
logger.error(f"Error while executing workflow: {exception}")
return Response(
{
"error": "Please check the logs for more details: " + str(exception)
"error": "Please check the logs for more details: "
+ str(exception)
},
status=status.HTTP_400_BAD_REQUEST
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as exception:
logger.error(f"Error while executing workflow: {exception}")
Expand Down Expand Up @@ -313,6 +314,11 @@ def clear_cache(
)
return Response(response.get("message"), status=response.get("status"))

@action(detail=True, methods=["get"])
def can_update(self, request: Request, pk: str) -> Response:
response: dict[str, Any] = WorkflowHelper.can_update_workflow(pk)
return Response(response, status=status.HTTP_200_OK)

@action(detail=True, methods=["get"])
def clear_file_marker(
self, request: Request, *args: Any, **kwargs: Any
Expand Down
27 changes: 22 additions & 5 deletions backend/workflow_manager/workflow/workflow_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@

from account.cache_service import CacheService
from account.models import Organization
from api.models import APIDeployment
from celery import current_task
from celery import exceptions as celery_exceptions
from celery import shared_task
from celery.result import AsyncResult
from django.conf import settings
from django.db import IntegrityError, connection
from django_tenants.utils import get_tenant_model, tenant_context
from pipeline.models import Pipeline
from redis import StrictRedis
from rest_framework import serializers
from tool_instance.constants import ToolInstanceKey
from tool_instance.models import ToolInstance
from tool_instance.tool_instance_helper import ToolInstanceHelper
from unstract.workflow_execution.enums import LogComponent, LogState
from unstract.workflow_execution.exceptions import StopExecution
from utils.request import feature_flag
from workflow_manager.endpoint.destination import DestinationConnector
from workflow_manager.endpoint.source import SourceConnector
from workflow_manager.workflow.constants import (
Expand Down Expand Up @@ -202,10 +203,10 @@ def run_workflow(
workflow_execution: Optional[WorkflowExecution] = None,
execution_mode: Optional[tuple[str, str]] = None,
) -> ExecutionResponse:
tool_instances: list[
ToolInstance
] = ToolInstanceHelper.get_tool_instances_by_workflow(
workflow.id, ToolInstanceKey.STEP
tool_instances: list[ToolInstance] = (
ToolInstanceHelper.get_tool_instances_by_workflow(
workflow.id, ToolInstanceKey.STEP
)
)
execution_mode = execution_mode or WorkflowExecution.Mode.INSTANT
execution_service = WorkflowHelper.build_workflow_execution_service(
Expand Down Expand Up @@ -652,6 +653,22 @@ def make_async_result(obj: AsyncResult) -> dict[str, Any]:
"info": obj.info,
}

@staticmethod
def can_update_workflow(workflow_id: str) -> dict[str, Any]:
try:
workflow: Workflow = Workflow.objects.get(pk=workflow_id)
if not workflow or workflow is None:
raise WorkflowDoesNotExistError()
used_count = Pipeline.objects.filter(workflow=workflow).count()
if used_count == 0:
used_count = APIDeployment.objects.filter(
workflow=workflow
).count()
return {"can_update": used_count == 0}
except Workflow.DoesNotExist:
logger.error(f"Error getting workflow: {id}")
raise WorkflowDoesNotExistError()


class WorkflowSchemaHelper:
"""Helper class for workflow schema related methods."""
Expand Down
28 changes: 21 additions & 7 deletions frontend/src/components/agency/actions/Actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,27 @@ function Actions({ statusBarMsg, initializeWfComp, stepLoader }) {
const axiosPrivate = useAxiosPrivate();

useEffect(() => {
setApiOpsPresent(source?.connection_type === "API");
}, [source]);

useEffect(() => {
setCanAddTaskPipeline(destination?.connection_type === "FILESYSTEM");
setCanAddETAPipeline(destination?.connection_type === "DATABASE");
}, [destination]);
// Enable Deploy as API only when
// Source & Destination connection_type are selected as API
setApiOpsPresent(
source?.connection_type === "API" &&
destination?.connection_type === "API"
);
// Enable Deploy as Task Pipeline only when
// destination connection_type is FILESYSTEM and Source & Destination are Configured
setCanAddTaskPipeline(
destination?.connection_type === "FILESYSTEM" &&
source?.connector_instance &&
destination?.connector_instance
);
// Enable Deploy as ETL Pipeline only when
// destination connection_type is DATABASE and Source & Destination are Configured
setCanAddETAPipeline(
destination?.connection_type === "DATABASE" &&
source?.connector_instance &&
destination.connector_instance
);
}, [source, destination]);

useEffect(() => {
if (stepExecType === wfExecutionTypes[1]) {
Expand Down
120 changes: 90 additions & 30 deletions frontend/src/components/agency/ds-settings-card/DsSettingsCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,16 @@ const inputOptions = [
value: "FILESYSTEM",
label: "File System",
},
{
value: "DATABASE",
label: "Database",
},
];

function DsSettingsCard({ type, endpointDetails, message, dependent }) {
const [options, setOptions] = useState([...inputOptions]);
function DsSettingsCard({ type, endpointDetails, message }) {
const workflowStore = useWorkflowStore();
const { source, destination, allowChangeEndpoint } = workflowStore;
const [options, setOptions] = useState({});
const [openModal, setOpenModal] = useState(false);

const [listOfConnectors, setListOfConnectors] = useState([]);
Expand All @@ -66,6 +72,30 @@ function DsSettingsCard({ type, endpointDetails, message, dependent }) {
output: <ExportOutlined className="ds-set-icon-size" />,
};

useEffect(() => {
if (type === "output") {
if (source?.connection_type === "") {
// Clear options when source is blank
setOptions({});
} else {
// Filter options based on source connection type
const filteredOptions = ["API"].includes(source?.connection_type)
? inputOptions.filter((option) => option.value === "API")
: inputOptions.filter((option) => option.value !== "API");

setOptions(filteredOptions);
}
}

if (type === "input") {
// Remove Database from Source Dropdown
const filteredOptions = inputOptions.filter(
(option) => option.value !== "DATABASE"
);
setOptions(filteredOptions);
}
}, [source, destination]);

useEffect(() => {
if (endpointDetails?.connection_type !== connType) {
setConnType(endpointDetails?.connection_type);
Expand All @@ -83,21 +113,6 @@ function DsSettingsCard({ type, endpointDetails, message, dependent }) {
getSourceDetails();
}, [endpointDetails]);

useEffect(() => {
if (type === "output") {
setOptions(() => {
const newOptions = [...inputOptions];
newOptions.push({
value: "DATABASE",
label: "Database",
});
return newOptions;
});
return;
}
setOptions([...inputOptions]);
}, [type]);

useEffect(() => {
const menuItems = [];
[...listOfConnectors].forEach((item) => {
Expand Down Expand Up @@ -174,6 +189,41 @@ function DsSettingsCard({ type, endpointDetails, message, dependent }) {
};
};

const clearDestination = (updatedData) => {
const body = { ...destination, ...updatedData };

const requestOptions = {
method: "PUT",
url: `/api/v1/unstract/${sessionDetails?.orgId}/workflow/endpoint/${destination?.id}/`,
headers: {
"X-CSRFToken": sessionDetails?.csrfToken,
"Content-Type": "application/json",
},
data: body,
};

axiosPrivate(requestOptions)
.then((res) => {
const data = res?.data || {};
const updatedData = {};
updatedData["destination"] = data;
updateWorkflow(updatedData);
})
.catch((err) => {
setAlertDetails(handleException(err, "Failed to update"));
});
};

const updateDestination = () => {
// Clear destination dropdown & data when input is switched
if (type === "input") {
clearDestination({
connection_type: "",
connector_instance: null,
});
}
};

const handleUpdate = (updatedData, showSuccess) => {
const body = { ...endpointDetails, ...updatedData };

Expand Down Expand Up @@ -237,19 +287,29 @@ function DsSettingsCard({ type, endpointDetails, message, dependent }) {
<Col span={12} className="ds-set-card-col2">
<SpaceWrapper>
<Space>
<Select
className="ds-set-card-select"
size="small"
options={options}
placeholder="Select Connector Type"
value={endpointDetails?.connection_type || undefined}
onChange={(value) =>
handleUpdate({
connection_type: value,
connector_instance: null,
})
<Tooltip
title={
!allowChangeEndpoint &&
"Workflow used in API/Task/ETL deployment"
}
/>
>
<Select
className="ds-set-card-select"
size="small"
options={options}
placeholder="Select Connector Type"
value={endpointDetails?.connection_type || undefined}
disabled={!allowChangeEndpoint}
onChange={(value) => {
handleUpdate({
connection_type: value,
connector_instance: null,
});
updateDestination();
}}
/>
</Tooltip>

<Tooltip
title={`${
endpointDetails?.connection_type
Expand Down Expand Up @@ -345,7 +405,7 @@ DsSettingsCard.propTypes = {
type: PropTypes.string.isRequired,
endpointDetails: PropTypes.object.isRequired,
message: PropTypes.string,
dependent: PropTypes.object.isRequired,
canUpdate: PropTypes.bool.isRequired,
};

export { DsSettingsCard };
23 changes: 21 additions & 2 deletions frontend/src/components/agency/steps/Steps.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,29 @@ function Steps({ steps, setSteps, activeToolId, sourceMsg, destinationMsg }) {

useEffect(() => {
getWfEndpointDetails();
canUpdateWorkflow();
}, []);

const canUpdateWorkflow = () => {
const requestOptions = {
method: "GET",
url: `/api/v1/unstract/${sessionDetails?.orgId}/workflow/${projectId}/can-update/`,
};
axiosPrivate(requestOptions)
.then((res) => {
const data = res?.data || {};
const body = {
allowChangeEndpoint: data?.can_update,
};
updateWorkflow(body);
})
.catch((err) => {
setAlertDetails(
handleException(err, "Failed to get can update status")
);
});
};

const moveItem = (fromIndex, toIndex, funcName, dragging) => {
if (fromIndex === undefined && funcName) {
handleAddToolInstance(funcName)
Expand Down Expand Up @@ -158,7 +179,6 @@ function Steps({ steps, setSteps, activeToolId, sourceMsg, destinationMsg }) {
type={sourceTypes.connectors[0]}
endpointDetails={source}
message={sourceMsg}
dependent={destination}
/>
<Divider className="wf-steps-div" />
</div>
Expand All @@ -185,7 +205,6 @@ function Steps({ steps, setSteps, activeToolId, sourceMsg, destinationMsg }) {
type={sourceTypes.connectors[1]}
endpointDetails={destination}
message={destinationMsg}
dependent={source}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from "../../../helpers/GetStaticData.js";
import { useAlertStore } from "../../../store/alert-store";
import { apiDeploymentsService } from "../../deployments/api-deployment/api-deployments-service.js";
import { useWorkflowStore } from "../../../store/workflow-store.js";

const defaultFromDetails = {
display_name: "",
Expand All @@ -27,6 +28,8 @@ const CreateApiDeploymentModal = ({
workflowId,
workflowEndpointList,
}) => {
const workflowStore = useWorkflowStore();
const { updateWorkflow } = workflowStore;
const apiDeploymentsApiService = apiDeploymentsService();
const { setAlertDetails } = useAlertStore();

Expand Down Expand Up @@ -97,7 +100,10 @@ const CreateApiDeploymentModal = ({
apiDeploymentsApiService
.createApiDeployment(body)
.then((res) => {
if (!workflowId) {
if (workflowId) {
// Update - can update workflow endpoint status in store
updateWorkflow({ allowChangeEndpoint: false });
} else {
updateTableData();
setSelectedRow(res?.data);
openCodeModal(true);
Expand Down
Loading

0 comments on commit 22a0c58

Please sign in to comment.