Skip to content

Commit c8f9c76

Browse files
committed
Merge branch 'chore_release-pd-8.5.0' into chore_mergeback-8.5.0-to-pd-branch
2 parents 99f465e + e445014 commit c8f9c76

File tree

4,319 files changed

+151179
-49643
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

4,319 files changed

+151179
-49643
lines changed

.github/workflows/components-test-build-deploy.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ jobs:
210210
VERSION_STRING=$(echo ${{ github.ref }} | sed 's/refs\/tags\/components@//')
211211
json -I -f ./components/package.json -e "this.version=\"$VERSION_STRING\""
212212
json -I -f ./components/package.json -e "this.dependencies['@opentrons/shared-data']=\"$VERSION_STRING\""
213+
json -I -f ./components/package.json -e "delete this.dependencies['@opentrons/step-generation']"
213214
- uses: 'actions/setup-node@v4'
214215
with:
215216
node-version: '22.11.0'

.github/workflows/opentrons-ai-client-staging-continuous-deploy.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
- edge
77
paths:
88
- 'opentrons-ai-client/**'
9+
workflow_dispatch:
910

1011
concurrency:
1112
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.run_id }}

.github/workflows/opentrons-ai-server-staging-continuous-deploy.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
- edge
77
paths:
88
- 'opentrons-ai-server/**'
9+
workflow_dispatch:
910

1011
concurrency:
1112
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.run_id }}

.github/workflows/pd-test-build-deploy.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,12 @@ jobs:
8383
- uses: ./.github/actions/js/setup
8484
- name: 'build PD'
8585
env:
86+
NODE_OPTIONS: '--max-old-space-size=5120'
8687
OT_PD_MIXPANEL_ID: ${{ secrets.OT_PD_MIXPANEL_ID }}
8788
OT_PD_MIXPANEL_DEV_ID: ${{ secrets.OT_PD_MIXPANEL_DEV_ID }}
89+
# ToDo(kk:04/23/2025) need to setup sentry org account and project
90+
# OT_PD_SENTRY_DSN: ${{ secrets.OT_PD_SENTRY_DNS }}
91+
# OT_PD_SENTRY_DEV_DSN: ${{ secrets.OT_PD_SENTRY_DEV_DSN }}
8892
run: |
8993
make -C protocol-designer NODE_ENV=development
9094
- name: 'upload github artifact'
@@ -93,6 +97,23 @@ jobs:
9397
name: 'pd-artifact'
9498
path: protocol-designer/dist
9599

100+
# ToDo(kk:04/23/2025) need to setup sentry org account and project
101+
# upload-sourcemaps-pd:
102+
# timeout-minutes: 10
103+
# name: 'upload protocol designer source maps'
104+
# needs: ['build-pd']
105+
# runs-on: 'ubuntu-24.04'
106+
# if: github.event_name != 'pull_request'
107+
108+
# uses: getsentry/action-release@v3
109+
# env:
110+
# SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
111+
# SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
112+
# SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
113+
# with:
114+
# name: pd-artifact
115+
# sourcemaps: ./dist
116+
96117
deploy-pd:
97118
timeout-minutes: 10
98119
name: 'deploy protocol designer'

.prettierrc.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,26 @@ module.exports = {
1212
jsxBracketSameLine: false, // default
1313
arrowParens: 'avoid', // default
1414
endOfLine: 'lf',
15+
plugins: ['@ianvs/prettier-plugin-sort-imports'],
16+
importOrder: [
17+
'^(react)(.*)$',
18+
'<THIRD_PARTY_MODULES>',
19+
'',
20+
'^@opentrons/(.*)$',
21+
'',
22+
'^/(.*)/(?!assets)(.*)$',
23+
'',
24+
'^[./](?!.*\\.(png|jpg|jpeg|gif|svg|webm|mp4)$)',
25+
'',
26+
'<TYPES>',
27+
'<TYPES>^(react)(.*)$',
28+
'<TYPES><THIRD_PARTY_MODULES>',
29+
'<TYPES>^@opentrons/(.*)$',
30+
'<TYPES>^/(.*)/(?!assets)(.*)$',
31+
'<TYPES>^[./]',
32+
'',
33+
'.*/assets/.*',
34+
'.*\\.(png|jpg|jpeg|gif|svg|webm|mp4)$',
35+
],
36+
importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'],
1537
}

.storybook/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ module.exports = {
44
'../app/**/*.stories.@(js|jsx|ts|tsx)',
55
'../protocol-designer/**/*.stories.@(js|jsx|ts|tsx)',
66
'../opentrons-ai-client/**/*.stories.@(js|jsx|ts|tsx)',
7+
'../components/**/*.mdx',
8+
'../app/**/*.mdx',
9+
'../protocol-designer/**/*.mdx',
10+
'../opentrons-ai-client/**/*.mdx',
711
],
812

913
addons: [

.storybook/preview.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { I18nextProvider } from 'react-i18next'
2+
23
import { GlobalStyle } from '../app/src/atoms/GlobalStyle'
34
import { i18n } from '../app/src/i18n'
45

@@ -46,10 +47,19 @@ export const customViewports = {
4647
name: 'Protocol Designer Base',
4748
type: 'desktop',
4849
styles: {
49-
width: '14402px',
50+
width: '1440px',
5051
height: '1024px',
5152
},
5253
},
54+
protocolDesignerSmallHeight: {
55+
// The small height for Protocol Designer. This might be the base size for web
56+
name: 'Protocol Designer Small Height',
57+
type: 'desktop',
58+
styles: {
59+
width: '1440px',
60+
height: '664px',
61+
},
62+
},
5363
}
5464

5565
export const parameters = {

__mocks__/electron-store.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// mock electron-store
22
'use strict'
3+
34
import { vi } from 'vitest'
45

56
// will by default mock the config dir. if you need other behaavior you can

__mocks__/electron-updater.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// mock electron-updater
22
'use strict'
3+
34
import { vi } from 'vitest'
5+
46
const EventEmitter = require('events')
57
const autoUpdater = new EventEmitter()
68

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
"""Slack Functions for Robot Communication."""
2+
from slack_sdk import WebClient
3+
from slack_sdk.web.slack_response import SlackResponse
4+
import configparser
5+
import os
6+
from typing import Optional, List, cast
7+
from opentrons.protocols.parameters.types import ParameterChoice
8+
9+
10+
class Slack:
11+
"""Slack Tool."""
12+
13+
def __init__(
14+
self,
15+
configuration: configparser.ConfigParser,
16+
channel_name: str,
17+
user_name: str,
18+
) -> None:
19+
"""Connects to slack channel."""
20+
slack_token = configuration["DEFAULT"]["slack_token"]
21+
self.client = WebClient(token=slack_token)
22+
self.channel = channel_name
23+
self.user_name = user_name
24+
# If user is a robot, then the icon will be the opentron logo
25+
if user_name.startswith("DVT") or user_name.startswith("PVT"):
26+
self.icon_emoji = ":robot_face:"
27+
else:
28+
self.icon_emoji = ":gear:"
29+
print("Using computer icon for non-robot user.")
30+
31+
def get_users_in_channel(self, channel_id: str) -> List[ParameterChoice]:
32+
"""Get all active, human users in a specific Slack channel."""
33+
all_member_ids: List[str] = []
34+
channel_cursor: Optional[str] = None
35+
36+
while True:
37+
response_members: SlackResponse = self.client.conversations_members(
38+
channel=channel_id, cursor=channel_cursor
39+
)
40+
if not cast(dict, response_members).get("ok", False):
41+
raise Exception("Failed to get channel members from Slack API.")
42+
43+
all_member_ids.extend(
44+
cast(list, cast(dict, response_members).get("members", []))
45+
)
46+
channel_cursor = cast(
47+
Optional[str],
48+
cast(dict, response_members)
49+
.get("response_metadata", {})
50+
.get("next_cursor"),
51+
)
52+
if not channel_cursor:
53+
break
54+
55+
user_list: List[ParameterChoice] = []
56+
user_cursor: Optional[str] = None
57+
58+
while True:
59+
response: SlackResponse = self.client.users_list(cursor=user_cursor)
60+
if not cast(dict, response).get("ok", False):
61+
raise Exception("Failed to get users from Slack API.")
62+
63+
users: List[dict] = cast(list, cast(dict, response).get("members", []))
64+
for user in users:
65+
user_id = user.get("id")
66+
profile = user.get("profile", {})
67+
if (
68+
user_id in all_member_ids
69+
and not user.get("deleted", False)
70+
and not user.get("is_bot", False)
71+
):
72+
first_name = profile.get("first_name", "").strip()
73+
last_name = profile.get("last_name", "").strip()
74+
display_name = (
75+
profile.get("real_name", "Unknown User")
76+
if not first_name and not last_name
77+
else f"{first_name} {last_name}".strip()
78+
)
79+
user_list.append(
80+
ParameterChoice(display_name=display_name, value=user_id)
81+
)
82+
user_cursor = cast(
83+
Optional[str],
84+
cast(dict, response).get("response_metadata", {}).get("next_cursor"),
85+
)
86+
if not user_cursor:
87+
break
88+
89+
return user_list
90+
91+
def send_slack_message(
92+
self,
93+
message: str,
94+
image_path: Optional[str] = None,
95+
user_id: Optional[str] = None,
96+
) -> None:
97+
"""Send slack message with or without image."""
98+
tagged_user = f"<@{user_id}> " if user_id and user_id.lower() != "none" else ""
99+
message = tagged_user + " " + message
100+
if image_path:
101+
response = self.client.files_upload(
102+
channels=self.channel,
103+
file=image_path,
104+
title=os.path.basename(image_path).split(".")[0],
105+
)
106+
response.validate()
107+
file_permalink = response["file"]["permalink"]
108+
self.client.chat_postMessage(
109+
channel=self.channel,
110+
blocks=[
111+
{
112+
"type": "image",
113+
"image_url": file_permalink,
114+
}
115+
],
116+
text=message,
117+
username=self.user_name,
118+
icon_emoji=self.icon_emoji,
119+
)
120+
else:
121+
self.client.chat_postMessage(
122+
channel=self.channel,
123+
text=message,
124+
username=self.user_name,
125+
icon_emoji=self.icon_emoji,
126+
)
127+
128+
def send_run_completed_message(
129+
self, protocol_name: str, user_id: Optional[str] = None
130+
) -> None:
131+
"""Send run completed message."""
132+
tagged_user = f"<@{user_id}> " if user_id and user_id.lower() != "none" else ""
133+
message = f"{tagged_user} {protocol_name} has completed successfully."
134+
self.send_slack_message(message)
135+
136+
def send_run_started_message(
137+
self, protocol_name: str, user_id: Optional[str] = None
138+
) -> None:
139+
"""Send run started message."""
140+
tagged_user = f"<@{user_id}> " if user_id and user_id.lower() != "none" else ""
141+
message = f"{tagged_user} Protocol: {protocol_name} has started."
142+
self.send_slack_message(message)
143+
144+
def send_error_message(
145+
self,
146+
protocol_name: str,
147+
error_str: str,
148+
image_path: Optional[str] = None,
149+
user_id: Optional[str] = None,
150+
) -> None:
151+
"""Send error message to Slack, optionally tagging a user."""
152+
tagged_user = f"<@{user_id}> " if user_id and user_id.lower() != "none" else ""
153+
message = f"{tagged_user} Protocol: {protocol_name} ended in error: {error_str}"
154+
155+
self.icon_emoji = ":alert:"
156+
self.send_slack_message(message, image_path)

0 commit comments

Comments
 (0)