diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index e8a6848b6f..4df7054988 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -5539,7 +5539,7 @@ pub async fn slack_app_callback_handler( let action_value = payload.actions[0].value.clone(); let response_url = payload.response_url; - let re = Regex::new(r"/api/w/(?P[^/]+)/jobs_u/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)\?approver=(?P[^&]+)").unwrap(); + let re = Regex::new(r"/api/w/(?P[^/]+)/jobs_u/(?Presume|cancel)/(?P[^/]+)/(?P[^/]+)/(?P[a-fA-F0-9]+)(?:\?approver=(?P[^&]+))?").unwrap(); if let Some(captures) = re.captures(&action_value) { let w_id = captures.name("w_id").map_or("", |m| m.as_str()); @@ -5664,7 +5664,7 @@ pub async fn request_slack_approval( let message_str = message.message.as_deref().unwrap_or("A flow is waiting for approval"); if let Some(resume_schema) = schema { - let hide_cancel = resume_schema.hide_cancel.unwrap_or(false); // Default to false if None + let hide_cancel = resume_schema.hide_cancel.unwrap_or(false); let schema_obj = match resume_schema.resume_form { Some(schema) => schema, diff --git a/python-client/build.sh b/python-client/build.sh index 90195376e2..6c9a723606 100755 --- a/python-client/build.sh +++ b/python-client/build.sh @@ -6,7 +6,13 @@ cp ../backend/windmill-api/openapi.yaml openapi/openapi.yaml npx @redocly/openapi-cli@latest bundle openapi/openapi.yaml > openapi-bundled.yaml -sed -z 's/FlowModuleValue:/FlowModuleValue2:/' openapi-bundled.yaml > openapi-decycled.yaml +if [[ "$OSTYPE" == "darwin"* ]]; then + # sed -z is not supported on macOS, use perl instead + perl -0777 -pe 's/FlowModuleValue:/FlowModuleValue2:/g' openapi-bundled.yaml > openapi-decycled.yaml +else + sed -z 's/FlowModuleValue:/FlowModuleValue2:/' openapi-bundled.yaml > openapi-decycled.yaml +fi + echo " FlowModuleValue: {}" >> openapi-decycled.yaml npx @redocly/openapi-cli@latest bundle openapi-decycled.yaml --ext json -d > openapi-deref.json @@ -20,9 +26,19 @@ rm -rf openapi/ rm openapi* cp LICENSE windmill-api/ -sed -i '5 i license = "Apache-2.0"' windmill-api/pyproject.toml -sed -i 's/authors = \[\]/authors = \["Ruben Fiszel "\]/g' windmill-api/pyproject.toml +# Check if running on macOS +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS version + sed -i '' '5 i\ +license = "Apache-2.0"' windmill-api/pyproject.toml + sed -i '' 's/authors = \[\]/\nauthors = \["Ruben Fiszel "\]/g' windmill-api/pyproject.toml +else + # Linux version + sed -i '5 i license = "Apache-2.0"' windmill-api/pyproject.toml + + sed -i 's/authors = \[\]/authors = \["Ruben Fiszel "\]/g' windmill-api/pyproject.toml +fi echo "# Autogenerated Windmill OpenApi Client" >> windmill-api/README.md.tmp echo "This is the raw autogenerated api client. You are most likely more interested \ @@ -33,8 +49,7 @@ user friendly experience. We use \ echo "" >> windmill-api/README.md.tmp - -head -n -13 windmill-api/README.md >> windmill-api/README.md.tmp +tail -r windmill-api/README.md | tail -n +14 | tail -r >> windmill-api/README.md.tmp mv windmill-api/README.md.tmp windmill-api/README.md cd windmill-api && poetry build diff --git a/python-client/dev.nu b/python-client/dev.nu index 52d68a7bec..11b92503de 100755 --- a/python-client/dev.nu +++ b/python-client/dev.nu @@ -1,6 +1,6 @@ #! /usr/bin/env nu -let cache = "/tmp/windmill/cache/pip/" +let cache = "/tmp/windmill/cache/python_311/" # Clean cache def "main clean" [] { @@ -42,7 +42,7 @@ def main [ rm -rf ($cache ++ wmill*/wmill/*) # Copy files from local ./dist to every wm-client version in cache - ls /tmp/windmill/cache/pip/wmill* | each { + ls /tmp/windmill/cache/python_311/wmill* | each { |i| let path = $i | get name; diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 99e1f351b1..3472fe410e 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -17,6 +17,7 @@ include = ["wmill/py.typed"] [tool.poetry.dependencies] python = "^3.7" httpx = ">=0.24" +slack-sdk = "^3.33.5" [build-system] requires = ["poetry>=1.0.2", "poetry-dynamic-versioning"] diff --git a/python-client/wmill/wmill/client.py b/python-client/wmill/wmill/client.py index c57e181a0f..884cde9ca7 100644 --- a/python-client/wmill/wmill/client.py +++ b/python-client/wmill/wmill/client.py @@ -16,6 +16,7 @@ import httpx from .s3_reader import S3BufferedReader, bytes_generator from .s3_types import Boto3ConnectionSettings, DuckDbConnectionSettings, PolarsConnectionSettings, S3Object +from slack_sdk import WebClient _client: "Windmill | None" = None @@ -623,6 +624,48 @@ class Windmill: params={"approver": approver}, ).json() + def request_interactive_slack_approval( + self, + slack_token: str, + channel: str, + message: str = None, + approver: str = None, + ) -> None: + """ + Request interactive Slack approval + :param slack_token: Slack token + :param channel: Slack channel + :param message: Message to send to Slack + :param approver: Approver name + """ + web = WebClient(slack_token) + nonce = random.randint(0, 4294967295) + workspace = self.workspace + flow_job_id = os.environ.get("WM_FLOW_JOB_ID") + + if not flow_job_id: + raise Exception( + "You can't use 'request_interactive_slack_approval' function in a standalone script or flow step preview. Please use it in a flow or a flow preview." + ) + + # Only include non-empty parameters + params = {} + if message: + params["message"] = message + if approver: + params["approver"] = approver + + blocks = self.get( + f"/w/{workspace}/jobs/slack_approval/{os.environ.get('WM_JOB_ID', 'NO_JOB_ID')}/{nonce}", + params=params, + ).json() + + web.chat_postMessage( + channel=channel, + text=message, + blocks=blocks, + ) + def username_to_email(self, username: str) -> str: """ Get email from workspace username @@ -972,6 +1015,19 @@ def get_state_path() -> str: def get_resume_urls(approver: str = None) -> dict: return _client.get_resume_urls(approver) +@init_global_client +def request_interactive_slack_approval( + slack_token: str, + channel: str, + message: str = None, + approver: str = None, +) -> dict: + return _client.request_interactive_slack_approval( + slack_token, + channel, + message, + approver, + ) @init_global_client def cancel_running() -> dict: diff --git a/typescript-client/client.ts b/typescript-client/client.ts index fa4e8ae70d..212ea8175a 100644 --- a/typescript-client/client.ts +++ b/typescript-client/client.ts @@ -873,15 +873,22 @@ export async function requestInteractiveSlackApproval({ ); } + // Only include non-empty parameters + const params: { approver?: string; message?: string } = {}; + if (message) { + params.message = message; + } + if (approver) { + params.approver = approver; + } + const blocks = await JobService.getSlackApprovalPayload({ workspace, resumeId: nonce, - approver, - message, + ...params, id: getEnv("WM_JOB_ID") ?? "NO_JOB_ID", }); - console.log("blocks", blocks); await web.chat.postMessage({ channel, text: message,