Skip to content

Commit

Permalink
Floppy disk save emoji improvements (#424)
Browse files Browse the repository at this point in the history
* Adding logic to add a link to the Slack message and an emoji at the begginng of each entry

* Formatting
  • Loading branch information
sylviamclaughlin authored Mar 8, 2024
1 parent eb1b71e commit ef49dc9
Show file tree
Hide file tree
Showing 6 changed files with 334 additions and 130 deletions.
189 changes: 125 additions & 64 deletions app/integrations/google_drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import base64
import logging
import datetime
import re

from dotenv import load_dotenv
from googleapiclient.discovery import build
Expand Down Expand Up @@ -312,6 +313,14 @@ def get_timeline_section(document_id):
text_run = elem.get("textRun")
if text_run:
text = text_run.get("content")
textStyle = text_run.get("textStyle", {})
if "link" in textStyle:
# Extract link URL
link = textStyle["link"].get("url")
# Format the text with the link as Markdown
formatted_text = f"[{text.strip()}]({link})"
# Replace the text with the formatted text
text = formatted_text
if START_HEADING in text:
record = True
found_start = True
Expand All @@ -326,6 +335,22 @@ def get_timeline_section(document_id):
return None if not (found_start and found_end) else timeline_content


def find_heading_indices(content, start_heading, end_heading):
"""Find the start and end indices of content between two headings."""
start_index, end_index = None, None
for element in content:
if "paragraph" in element:
text_runs = element["paragraph"].get("elements", [])
for text_run in text_runs:
text = text_run.get("textRun", {}).get("content", "")
if start_heading in text:
start_index = text_run.get("endIndex")
elif end_heading in text and start_index is not None:
end_index = text_run.get("startIndex")
return start_index, end_index
return start_index, end_index


# Replace the text between the headings
def replace_text_between_headings(doc_id, new_content, start_heading, end_heading):
# Setup the service
Expand All @@ -336,83 +361,119 @@ def replace_text_between_headings(doc_id, new_content, start_heading, end_headin
content = document.get("body").get("content")

# Find the start and end indices
start_index = None
end_index = None
for element in content:
if "paragraph" in element:
paragraph = element.get("paragraph")
text_runs = paragraph.get("elements")
for text_run in text_runs:
text = text_run.get("textRun").get("content")
if start_heading in text:
# Set start_index to the end of the start heading
start_index = text_run.get("endIndex")
if end_heading in text and start_index is not None:
# Set end_index to the start of the end heading
end_index = text_run.get("startIndex")
break
start_index, end_index = find_heading_indices(content, start_heading, end_heading)

if start_index is not None and end_index is not None:
# Format new content with new lines for proper insertion. We need to make sure that the formatted string contains only one
# leading and trailing newline character
# Split the string into three parts: leading newlines, core content, and trailing newlines
leading_newlines = len(new_content) - len(new_content.lstrip("\n"))
trailing_newlines = len(new_content) - len(new_content.rstrip("\n"))
core_content = new_content[
leading_newlines : len(new_content) - trailing_newlines
]

# Ensure only one newline at the start and one at the end, preserving internal newlines
formatted_content = "\n" + core_content + "\n"
content_length = len(formatted_content)

# Perform the replacement
# Delete the existing content from the document
requests = [
{
"deleteContentRange": {
"range": {"startIndex": start_index, "endIndex": end_index}
}
},
{
"insertText": {
"location": {"index": start_index},
"text": formatted_content,
}
},
]
# Format the inserted text - we want to make sure that the font size is what we want
requests.append(
{
"updateTextStyle": {
"range": {
"startIndex": start_index,
"endIndex": (
start_index + content_length
), # Adjust this index based on the length of the text
},
"textStyle": {
"fontSize": {"magnitude": 11, "unit": "PT"},
"bold": False,
},
"fields": "bold",
}
}
)
# Update paragraph style to be normal text
]

# split the formatted content by the emoji
line = new_content.split(" ➡ ")
pattern = r"\[([^\]]+)\]\(([^)]+)\)\s([^:]+):\s(.+)"
insert_index = start_index
inserted_content = ""

# Insert an empty line before the new content and after the placeholder text
text_to_insert = "\n"
text_len = len(text_to_insert)
requests.append(
{
"updateParagraphStyle": {
"range": {
"startIndex": start_index,
"endIndex": (
start_index + content_length
), # Adjust this index based on the length of the text
},
"paragraphStyle": {"namedStyleType": "NORMAL_TEXT"},
"fields": "namedStyleType",
"insertText": {
"location": {"index": insert_index},
"text": text_to_insert,
}
}
)
# udpate the insert index
insert_index += text_len

for item in line:
# split the item by the emoji and strip out any empty strings
original_entries = item.split("➡️ ")
entries = [entry for entry in original_entries if entry.strip()]

for entry in entries:
# Regular expression to match the entry pattern
pattern = r"\[(?P<date>.+?) ET\]\((?P<url>.+?)\) (?P<name>.+?): (?P<message>.+)$"

# Use re.DOTALL to make '.' match newline characters as well. This is needed for multi-line messages
match = re.match(pattern, entry, re.DOTALL)

if match:
# Extract components from the match object
date = match.group("date") + " ET"
url = match.group("url")
name = match.group("name")
message = match.group(
"message"
).strip() # Remove leading/trailing whitespace

# Construct the text to be inserted with the date as a link
text_to_insert = f" ➡️ {date} {name}: {message}\n"
text_len = len(text_to_insert)
inserted_content += text_to_insert

# Insert text request
requests.append(
{
"insertText": {
"location": {"index": insert_index},
"text": text_to_insert,
}
}
)
# Update link style for date_text
requests.append(
{
"updateTextStyle": {
"range": {
"startIndex": insert_index + 4,
"endIndex": insert_index + len(date) + 4,
},
"textStyle": {"link": {"url": url}},
"fields": "link",
}
}
)
# Update for next insertion
insert_index += text_len
else:
# if we don't match the above pattern, just insert the entry as is
text_to_insert = f" ➡️ {item}\n"
inserted_content += text_to_insert
text_len = len(text_to_insert)
# Insert text request for the entire block of formatted_content
requests.append(
{
"insertText": {
"location": {"index": insert_index},
"text": text_to_insert,
}
}
)

# Update insert_index as needed, assuming formatted_content is a single block of text
insert_index += text_len

# Make sure that we do normal formatting for the inserted content
requests.append(
{
"updateParagraphStyle": {
"range": {
"startIndex": start_index,
"endIndex": (start_index + len(inserted_content)),
},
"paragraphStyle": {"namedStyleType": "NORMAL_TEXT"},
"fields": "namedStyleType",
}
}
)
service.documents().batchUpdate(
documentId=doc_id, body={"requests": requests}
).execute()
Expand Down
62 changes: 22 additions & 40 deletions app/modules/incident/handle_slack_message_reactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,37 @@


def rearrange_by_datetime_ascending(text):
# Split the text by lines
lines = text.split("\n")

# Temporary storage for multiline entries
entries = []
current_entry = []

# Iterate over each line
for line in lines:
# Check if the line starts with a datetime format including 'ET'
if re.match(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ET", line):
if current_entry:
# Combine the lines in current_entry and add to entries
entries.append("\n".join(current_entry))
current_entry = [line]
else:
current_entry.append(line)
else:
# If not a datetime, it's a continuation of the previous message
current_entry.append(line)

# Add the last entry
if current_entry:
if current_entry.__len__() > 1:
# that means we have a multiline entry
joined_current_entry = "\n".join(current_entry)
entries.append(joined_current_entry)
else:
entries.append("\n".join(current_entry))
pattern = r"\s*➡️\s*\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) ET\]\((https?://[\w./-]+)\)\s([\w\s]+):\s"

# Now extract date, time, and message from each entry
dated_entries = []
for entry in entries:
match = re.match(
r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ET):?[\s,]*(.*)", entry, re.DOTALL
)
current_message = []
for line in lines:
match = re.match(pattern, line)
if match:
date_str, msg = match.groups()
# Parse the datetime string (ignoring 'ET' for parsing)
dt = datetime.strptime(date_str[:-3].strip(), "%Y-%m-%d %H:%M:%S")
dated_entries.append((dt, msg))
if (
current_message
): # If there's a current message, finalize it before starting a new one
entries.append(current_message)
current_message = []
date_str, url, name = match.groups()
dt = datetime.strptime(date_str.strip(), "%Y-%m-%d %H:%M:%S")
msg_start = line[match.end() :].strip(" ")
current_message = [dt, url, f"{name}:", msg_start]
elif current_message: # If it's a continuation of the current message
current_message[-1] += "\n" + f"{line.strip()}"

if current_message: # Don't forget to append the last message
entries.append(current_message)

# Sort the entries by datetime in ascending order
sorted_entries = sorted(dated_entries, key=lambda x: x[0], reverse=False)
sorted_entries = sorted(entries, key=lambda x: x[0])

# Reformat the entries back into strings, including 'ET'
sorted_text = "\n".join(
# Reformat the entries back into strings, including 'ET' and the full message
sorted_text = "\n\n".join(
[
f"{entry[0].strftime('%Y-%m-%d %H:%M:%S')} ET {entry[1]}"
f"➡️ [{entry[0].strftime('%Y-%m-%d %H:%M:%S')} ET]({entry[1]}) {entry[2]} {entry[3]}"
for entry in sorted_entries
]
)
Expand Down
31 changes: 27 additions & 4 deletions app/modules/incident/incident.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,19 @@ def handle_reaction_added(client, ack, body, logger):
)
if document_id == "":
logger.error("No incident document found for this channel.")

for message in messages:
# get the message ts time
message_ts = message["ts"]

# convert the time which is now in epoch time to standard ET Time
message_date_time = convert_epoch_to_datetime_est(message["ts"])
message_date_time = convert_epoch_to_datetime_est(message_ts)

# get a link to the message
link = client.chat_getPermalink(
channel=channel_id, message_ts=message_ts
)["permalink"]

# get the user name from the message
user = client.users_profile_get(user=message["user"])
# get the full name of the user so that we include it into the timeline
Expand All @@ -462,7 +472,9 @@ def handle_reaction_added(client, ack, body, logger):
# if the message already exists in the timeline, then don't put it there again
if content and message_date_time not in content:
# append the new message to the content
content += f"{message_date_time} {user_full_name}: {message}"
content += (
f" ➡️ [{message_date_time}]({link}) {user_full_name}: {message}"
)

# if there is an image in the message, then add it to the timeline
if "files" in message:
Expand Down Expand Up @@ -500,8 +512,11 @@ def handle_reaction_removed(client, ack, body, logger):
# get the message we want to delete
message = messages[0]

# get the message ts time
message_ts = message["ts"]

# convert the epoch time to standard ET day/time
message_date_time = convert_epoch_to_datetime_est(message["ts"])
message_date_time = convert_epoch_to_datetime_est(message_ts)

# get the user of the person that send the message
user = client.users_profile_get(user=message["user"])
Expand All @@ -528,8 +543,16 @@ def handle_reaction_removed(client, ack, body, logger):
user_handle, message["text"]
)

# get a link to the message
link = client.chat_getPermalink(channel=channel_id, message_ts=message_ts)[
"permalink"
]

# Construct the message to remove
message_to_remove = f"\n{message_date_time} {user_full_name}: {message}\n"
message_to_remove = (
f" ➡️ [{message_date_time}]({link}) {user_full_name}: {message}\n"
)

# if there is a file in the message, then add it to the message to remove
if "files" in message:
image = message["files"][0]["url_private"]
Expand Down
Loading

0 comments on commit ef49dc9

Please sign in to comment.