Skip to content

Commit

Permalink
Merge branch 'edge' into exec-201-strict-mypy-api
Browse files Browse the repository at this point in the history
  • Loading branch information
sfoster1 committed Aug 7, 2024
2 parents 8fd2f32 + 88b6a17 commit 048178c
Show file tree
Hide file tree
Showing 407 changed files with 4,751 additions and 2,969 deletions.
2 changes: 1 addition & 1 deletion abr-testing/abr_testing/automation/google_sheets_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def open_worksheet(self, tab_number: int) -> Any:
def create_worksheet(self, title: str) -> Optional[str]:
"""Create a worksheet with tab name. Existing spreadsheet needed."""
try:
new_sheet = self.spread_sheet.add_worksheet(title, rows="2500", cols="40")
new_sheet = self.spread_sheet.add_worksheet(title, rows="2500", cols="50")
return new_sheet.id
except gspread.exceptions.APIError:
print("Sheet already exists.")
Expand Down
69 changes: 62 additions & 7 deletions abr-testing/abr_testing/automation/jira_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,79 @@ def __init__(self, url: str, api_token: str, email: str) -> None:
"Content-Type": "application/json",
}

def issues_on_board(self, board_id: str) -> List[str]:
def issues_on_board(self, project_key: str) -> List[List[Any]]:
"""Print Issues on board."""
params = {"jql": f"project = {project_key}"}
response = requests.get(
f"{self.url}/rest/agile/1.0/board/{board_id}/issue",
f"{self.url}/rest/api/3/search",
headers=self.headers,
params=params,
auth=self.auth,
)

response.raise_for_status()
try:
board_data = response.json()
all_issues = board_data["issues"]
except json.JSONDecodeError as e:
print("Error decoding json: ", e)
# convert issue id's into array and have one key as
# the issue key and one be summary, return entire array
issue_ids = []
for i in all_issues:
issue_id = i.get("id")
issue_ids.append(issue_id)
issue_summary = i["fields"].get("summary")
issue_ids.append([issue_id, issue_summary])
return issue_ids

def match_issues(self, issue_ids: List[List[str]], ticket_summary: str) -> List:
"""Matches related ticket ID's."""
to_link = []
error = ticket_summary.split("_")[3]
robot = ticket_summary.split("_")[0]
# for every issue see if both match, if yes then grab issue ID and add it to a list
for issue in issue_ids:
summary = issue[1]
try:
issue_error = summary.split("_")[3]
issue_robot = summary.split("_")[0]
except IndexError:
continue
issue_id = issue[0]
if robot == issue_robot and error == issue_error:
to_link.append(issue_id)
return to_link

def link_issues(self, to_link: list, ticket_key: str) -> None:
"""Links relevant issues in Jira."""
for issue in to_link:
link_data = json.dumps(
{
"inwardIssue": {"key": ticket_key},
"outwardIssue": {"id": issue},
"type": {"name": "Relates"},
}
)
try:
response = requests.post(
f"{self.url}/rest/api/3/issueLink",
headers=self.headers,
auth=self.auth,
data=link_data,
)
response.raise_for_status()
except requests.exceptions.HTTPError:
print(
f"HTTP error occurred. Ticket ID {issue} was not linked. \
Check user permissions and authentication credentials"
)
except requests.exceptions.ConnectionError:
print(f"Connection error occurred. Ticket ID {issue} was not linked.")
except json.JSONDecodeError:
print(
f"JSON decoding error occurred. Ticket ID {issue} was not linked."
)

def open_issue(self, issue_key: str) -> str:
"""Open issue on web browser."""
url = f"{self.url}/browse/{issue_key}"
Expand Down Expand Up @@ -116,17 +170,18 @@ def create_ticket(

def post_attachment_to_ticket(self, issue_id: str, attachment_path: str) -> None:
"""Adds attachments to ticket."""
# TODO: Ensure that file is actually uploaded.
file = {"file": open(attachment_path, "rb")}
JSON_headers = {"Accept": "application/json"}
file = {
"file": (attachment_path, open(attachment_path, "rb"), "application-type")
}
JSON_headers = {"Accept": "application/json", "X-Atlassian-Token": "no-check"}
try:
response = requests.post(
f"{self.url}/rest/api/3/issue/{issue_id}/attachments",
headers=JSON_headers,
auth=self.auth,
files=file,
)
print(response)
print(f"File: {attachment_path} posted to ticket {issue_id}")
except json.JSONDecodeError:
error_message = str(response.content)
print(f"JSON decoding error occurred. Response content: {error_message}.")
Expand Down
17 changes: 13 additions & 4 deletions abr-testing/abr_testing/data_collection/abr_robot_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,6 @@ def get_run_error_info_from_robot(
reporter_id = args.reporter_id[0]
file_paths = read_robot_logs.get_logs(storage_directory, ip)
ticket = jira_tool.JiraTicket(url, api_token, email)
ticket.issues_on_board(board_id)
users_file_path = ticket.get_jira_users(storage_directory)
assignee_id = get_user_id(users_file_path, assignee)
run_log_file_path = ""
Expand Down Expand Up @@ -519,6 +518,9 @@ def get_run_error_info_from_robot(
print(robot)
parent_key = project_key + "-" + robot.split("ABR")[1]

# Grab all previous issues
all_issues = ticket.issues_on_board(project_key)

# TODO: read board to see if ticket for run id already exists.
# CREATE TICKET
issue_key, raw_issue_url = ticket.create_ticket(
Expand All @@ -533,10 +535,12 @@ def get_run_error_info_from_robot(
affects_version,
parent_key,
)
# Link Tickets
to_link = ticket.match_issues(all_issues, summary)
ticket.link_issues(to_link, issue_key)
# OPEN TICKET
issue_url = ticket.open_issue(issue_key)
# MOVE FILES TO ERROR FOLDER.

error_files = [saved_file_path_calibration, run_log_file_path] + file_paths
error_folder_path = os.path.join(storage_directory, issue_key)
os.makedirs(error_folder_path, exist_ok=True)
Expand All @@ -548,8 +552,11 @@ def get_run_error_info_from_robot(
shutil.move(source_file, destination_file)
except shutil.Error:
continue
# OPEN FOLDER DIRECTORY
subprocess.Popen(["explorer", error_folder_path])
# POST FILES TO TICKET
list_of_files = os.listdir(error_folder_path)
for file in list_of_files:
file_to_attach = os.path.join(error_folder_path, file)
ticket.post_attachment_to_ticket(issue_key, file_to_attach)
# ADD ERROR COMMENTS TO TICKET
read_each_log(error_folder_path, raw_issue_url)
# WRITE ERRORED RUN TO GOOGLE SHEET
Expand Down Expand Up @@ -594,3 +601,5 @@ def get_run_error_info_from_robot(
google_sheet_lpc.batch_update_cells(runs_and_lpc, "A", start_row_lpc, "0")
else:
print("Ticket created.")
# Open folder directory incase uploads to ticket were incomplete
subprocess.Popen(["explorer", error_folder_path])
17 changes: 17 additions & 0 deletions api-client/src/dataFiles/getCsvFileRaw.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { GET, request } from '../request'

import type { DownloadedCsvFileResponse } from './types'
import type { ResponsePromise } from '../request'
import type { HostConfig } from '../types'

export function getCsvFileRaw(
config: HostConfig,
fileId: string
): ResponsePromise<DownloadedCsvFileResponse> {
return request<DownloadedCsvFileResponse>(
GET,
`/dataFiles/${fileId}/download`,
null,
config
)
}
1 change: 1 addition & 0 deletions api-client/src/dataFiles/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { getCsvFileRaw } from './getCsvFileRaw'
export { uploadCsvFile } from './uploadCsvFile'

export * from './types'
6 changes: 3 additions & 3 deletions api-client/src/dataFiles/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface UploadedCsvFileResponse {
}

export interface UploadedCsvFilesResponse {
data: {
files: CsvFileData[]
}
data: CsvFileData[]
}

export type DownloadedCsvFileResponse = string
2 changes: 2 additions & 0 deletions api-client/src/runs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ export { createRunAction } from './createRunAction'
export * from './createLabwareOffset'
export * from './createLabwareDefinition'
export * from './constants'
export * from './updateErrorRecoveryPolicy'

export * from './types'
export type { CreateRunData } from './createRun'
27 changes: 27 additions & 0 deletions api-client/src/runs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface LegacyGoodRunData {
status: RunStatus
actions: RunAction[]
errors: RunError[]
hasEverEnteredErrorRecovery: boolean
pipettes: LoadedPipette[]
labware: LoadedLabware[]
liquids: Liquid[]
Expand Down Expand Up @@ -146,3 +147,29 @@ export interface CommandData {
// Although run errors are semantically different from command errors,
// the server currently happens to use the exact same model for both.
export type RunError = RunCommandError

/**
* Error Policy
*/

export type IfMatchType = 'ignoreAndContinue' | 'failRun' | 'waitForRecovery'

export interface ErrorRecoveryPolicy {
policyRules: Array<{
matchCriteria: {
command: {
commandType: RunTimeCommand['commandType']
error: {
errorType: RunCommandError['errorType']
}
}
}
ifMatch: IfMatchType
}>
}

export interface UpdateErrorRecoveryPolicyRequest {
data: ErrorRecoveryPolicy
}

export type UpdateErrorRecoveryPolicyResponse = Record<string, never>
48 changes: 48 additions & 0 deletions api-client/src/runs/updateErrorRecoveryPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { PUT, request } from '../request'

import type { HostConfig } from '../types'
import type { ResponsePromise } from '../request'
import type {
ErrorRecoveryPolicy,
IfMatchType,
UpdateErrorRecoveryPolicyRequest,
UpdateErrorRecoveryPolicyResponse,
} from './types'
import type { RunCommandError, RunTimeCommand } from '@opentrons/shared-data'

export type RecoveryPolicyRulesParams = Array<{
commandType: RunTimeCommand['commandType']
errorType: RunCommandError['errorType']
ifMatch: IfMatchType
}>

export function updateErrorRecoveryPolicy(
config: HostConfig,
runId: string,
policyRules: RecoveryPolicyRulesParams
): ResponsePromise<UpdateErrorRecoveryPolicyResponse> {
const policy = buildErrorRecoveryPolicyBody(policyRules)

return request<
UpdateErrorRecoveryPolicyResponse,
UpdateErrorRecoveryPolicyRequest
>(PUT, `/runs/${runId}/errorRecoveryPolicy`, { data: policy }, config)
}

function buildErrorRecoveryPolicyBody(
policyRules: RecoveryPolicyRulesParams
): ErrorRecoveryPolicy {
return {
policyRules: policyRules.map(rule => ({
matchCriteria: {
command: {
commandType: rule.commandType,
error: {
errorType: rule.errorType,
},
},
},
ifMatch: rule.ifMatch,
})),
}
}
1 change: 1 addition & 0 deletions api/docs/v2/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,5 +446,6 @@
("py:class", r".*opentrons_shared_data.*"),
("py:class", r".*protocol_api._parameters.Parameters.*"),
("py:class", r".*AbsorbanceReaderContext"), # shh it's a secret (for now)
("py:class", r".*RobotContext"), # shh it's a secret (for now)
("py:class", r'.*AbstractLabware|APIVersion|LabwareLike|LoadedCoreMap|ModuleTypes|NoneType|OffDeckType|ProtocolCore|WellCore'), # laundry list of not fully qualified things
]
10 changes: 10 additions & 0 deletions api/release-notes-internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ For more details about this release, please see the full [technical change log][

[technical change log]: https://github.com/Opentrons/opentrons/releases

## Internal Release 2.0.0-alpha.4

This internal release, pulled from the `edge` branch, contains features being developed for 8.0.0. It's for internal testing only. There are no changes to `buildroot`, `ot3-firmware`, or `oe-core` since the last internal release.

- [Opentrons changes since the latest stable release](https://github.com/Opentrons/opentrons/compare/[email protected])
- [Opentrons changes since the last internal release](https://github.com/Opentrons/opentrons/compare/[email protected]@2.0.0-alpha.4)
- [Flex changes since last stable release](https://github.com/Opentrons/oe-core/compare/[email protected])
- [Flex firmware changes since last stable release](https://github.com/Opentrons/ot3-firmware/compare/v52...internal@v10)
- [OT2 changes since last stable release](https://github.com/Opentrons/buildroot/compare/[email protected])

## Internal Release 2.0.0-alpha.3

This internal release, pulled from the `edge` branch, contains features being developed for 8.0.0. It's for internal testing only.
Expand Down
14 changes: 11 additions & 3 deletions api/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@ log][]. For a list of currently known issues, please see the [Opentrons issue tr

---

## Opentrons Robot Software Changes in 7.4.0
## Opentrons Robot Software Changes in 7.5.0

Welcome to the v7.4.0 release of the Opentrons robot software!
Welcome to the v7.5.0 release of the Opentrons robot software!

This release adds support for the [Opentrons Flex HEPA/UV Module](https://opentrons.com/products/opentrons-flex-hepa-uv-module).
### Hardware Support

- [Opentrons Flex HEPA/UV Module](https://opentrons.com/products/opentrons-flex-hepa-uv-module)
- Latest Flex Gripper model (serial numbers beginning `GRPV13`)

### Bug Fixes

- Fixed certain string runtime parameter values being misinterpreted as an incorrect type.

### Known Issue

- The HEPA/UV Module's buttons may not respond properly after its safety shutoff is activated. This happens when the module is removed from the top of Flex while its lights are on. Power cycle the module to restore normal behavior. The module is safe to use even if you do not power cycle it.

---

## Opentrons Robot Software Changes in 7.3.1
Expand Down
1 change: 1 addition & 0 deletions api/src/opentrons/cli/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ async def _do_analyze(protocol_source: ProtocolSource) -> RunResult:
modules=[],
labwareOffsets=[],
liquids=[],
hasEverEnteredErrorRecovery=False,
),
parameters=[],
)
Expand Down
Loading

0 comments on commit 048178c

Please sign in to comment.