Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove edit of alternate phrasing #330

Merged
merged 1 commit into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions api/coda.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,18 +300,6 @@ def update_question_tags(self, question: QuestionRow, new_tags: list[str]) -> No
self.questions_df.at[question["id"], "tags"].extend(new_tags)
self.last_question_id = question["id"]

# Alternate phrasings

def update_question_altphr(
self, question: QuestionRow, new_alt_phrs: list[str]
) -> None:
self.doc.get_table(self.STAMPY_ANSWERS_API_ID).update_row(
question["row"], make_updated_cells({"Alternate Phrasings": new_alt_phrs})
)
self.questions_df.at[question["id"], "alternate_phrasings"].clear()
self.questions_df.at[question["id"], "alternate_phrasings"].extend(new_alt_phrs)
self.last_question_id = question["id"]

###############
# Finding #
###############
Expand Down
145 changes: 24 additions & 121 deletions modules/question_setter.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,12 @@
`s, <set/change> <status/to/status to> <status> <gdoc-link(s)>`
`s, <set/change> <status/to/status to> <status> question <question-title>` - change status of a question fuzzily matching that title

Editing tags or alternate phrasings, Tags, Alternate phrasings, Altphr
Add a tag or an alternate phrasing to a question (specified by title, GDocLink, or the last one)
Editing tags, Tags
Add a tag to a question (specified by title, GDocLink, or the last one)
`s, <add/add tag> <tag-name> <gdoc-link(s)/question-title>` - specified by gdoc-links or question title (doesn't matter whether you put `<tag-name>` or `<gdoc-links/question-title>` first)
`s, <tag/add tag> <tag-name>` - if you don't specify the question, Stampy assumes you refer to the last one
`s, <delete/del/remove/rm> <tag-name> <gdoc-links/question-title>` - removing tags
`s, clear tags <gdoc-links/question-title>` - clear all tags on a question
`s, <altphr> "<alternate-phrasing>" <gdoc-link/question-title>` - you must put the alternate phrasing in double quotes and can do it only on one question at a time
`s <delete/del/remove/rm> <alternate phrasing/alt> "<alternate-phrasing>" <gdoc-link/question-title>` - analogously
`s, clear altphr` - here, on last question
"""
from __future__ import annotations

Expand Down Expand Up @@ -103,13 +100,6 @@ def __init__(self) -> None:
self.re_add_tag = re.compile(r"(add\s)?tag", re.I)
self.re_remove_tag = re.compile(r"(delete|del|remove|rm)\stag", re.I)

# altphr
alt_phr_pat = "(alt|alternate|alt phrasing|alternate phrasing|alias)"
self.re_add_alt_phr = re.compile(r"(add )?" + alt_phr_pat, re.I)
self.re_remove_alt_phr = re.compile(
r"(delete|del|remove|rm) " + alt_phr_pat, re.I
)

# status
status_pat = "|".join(self.coda_api.status_shorthand_dict)
self.re_status = re.compile(
Expand Down Expand Up @@ -174,10 +164,6 @@ def process_message(self, message: ServiceMessage) -> Response:
if response := self.parse_edit_tag(text, message):
return response

# alternate phrasings
if response := self.parse_edit_altphr(text, message):
return response

# even if message is not `at me`, it may contain GDoc links
if gdoc_links := parse_gdoc_links(text):
self.msg_id2gdoc_links[str(message.id)] = gdoc_links
Expand Down Expand Up @@ -370,9 +356,9 @@ async def cb_question_approval(
why=f"I set {n_new_los} questions to `Live on site` because {message.author} approved them.",
)

#######################################################
# Adding/removing tags and alternative phrasings #
#######################################################
############################
# Adding/removing tags #
############################

def parse_edit_tag(self, text: str, message: ServiceMessage) -> Optional[Response]:
if self.re_add_tag.match(text):
Expand All @@ -394,144 +380,102 @@ def parse_edit_tag(self, text: str, message: ServiceMessage) -> Optional[Respons
query = "Last", "DEFAULT"
return Response(
confidence=10,
callback=self.cb_edit_tag_or_altphr,
args=[query, tag, message, edit_action, "tag"],
)

def parse_edit_altphr(
self, text: str, message: ServiceMessage
) -> Optional[Response]:
if self.re_add_alt_phr.match(text):
edit_action: EditAction = "add"
elif self.re_remove_alt_phr.match(text):
edit_action = "remove"
elif text.startswith("clear alt"):
edit_action = "clear"
else:
return

if edit_action == "clear":
alt_phr = None
elif not (alt_phr := parse_alt_phr(text)):
return

query = parse_question_spec_query(text)
if query is None:
query = "Last", "DEFAULT"
return Response(
confidence=10,
callback=self.cb_edit_tag_or_altphr,
args=[query, alt_phr, message, edit_action, "alternate phrasing"],
callback=self.cb_edit_tag,
args=[query, tag, message, edit_action],
)

async def cb_edit_tag_or_altphr(
async def cb_edit_tag(
self,
query: QuestionSpecQuery,
val: Optional[str], # tag or altphr (None only if edit_action == "clear")
message: ServiceMessage,
edit_action: EditAction,
tag_or_altphr: Literal["tag", "alternate phrasing"],
) -> Response:
if not has_permissions(message.author):
return Response(
confidence=10,
text=f"You don't have permissions required to edit {tag_or_altphr}s <@{message.author}>",
why=f"{message.author.display_name} does not have permissions edit {tag_or_altphr}s on questions",
text=f"You don't have permissions required to edit tags <@{message.author}>",
why=f"{message.author.display_name} does not have permissions edit tags on questions",
)

# inserts for generating messages
to_from_on = {"add": "to", "remove": "from", "clear": "on"}[edit_action]
verb_gerund = {"add": "Adding", "remove": "Removing", "clear": "Clearing"}[edit_action] # fmt:skip
field = "tags" if tag_or_altphr == "tag" else "alternate_phrasings"
field = "tags"
questions = await self.coda_api.query_for_questions(query, message)

if not questions:
Response(
confidence=10,
text=f"I found no questions conforming to the query\n{pformat_to_codeblock(dict([query]))}",
why=f"{message.author.display_name} asked me to {edit_action} {tag_or_altphr} `{val}` {to_from_on} some question(s) but I found nothing",
)
# adding/removing one altphr per many questions is not allowed
if (
len(questions) > 1
and edit_action != "clear"
and tag_or_altphr == "alternate phrasing"
):
return Response(
confidence=10,
text=f"I don't think you want to {edit_action} the same alternate phrasing {to_from_on} {len(questions)} questions. Please, choose one.",
why=f"{message.author.display_name} asked me to more than one question at once which is not the way to go",
why=f"{message.author.display_name} asked me to {edit_action} tags `{val}` {to_from_on} some question(s) but I found nothing",
)

if edit_action != "clear":
msg = f"{verb_gerund} {tag_or_altphr} `{val}` {to_from_on} "
msg = f"{verb_gerund} tag `{val}` {to_from_on} "
else:
msg = f"Clearing {tag_or_altphr}s on "
msg = f"Clearing tags on "
msg += f"{len(questions)} questions" if len(questions) > 1 else "one question"
await message.channel.send(msg)

n_edited = 0
update_method: Callable[[QuestionRow, list[str]], None] = (
self.coda_api.update_question_tags
if tag_or_altphr == "tag"
else self.coda_api.update_question_altphr
)

if edit_action == "add":
val = cast(str, val)
for q in questions:
if val in q[field]:
await message.channel.send(
f'"{q["title"]}" already has this {tag_or_altphr}'
f'"{q["title"]}" already has this tag'
)
else:
update_method(q, q[field] + [val])
n_edited += 1
await message.channel.send(
f'Added {tag_or_altphr} `{val}` to "{q["title"]}"'
f'Added tag `{val}` to "{q["title"]}"'
)
elif edit_action == "remove":
for q in questions:
if val not in q[field]:
await message.channel.send(
f'"{q["title"]}" doesn\'t have this {tag_or_altphr}'
f'"{q["title"]}" doesn\'t have this tag'
)
else:
new_tags = [t for t in q[field] if t != val]
update_method(q, new_tags)
n_edited += 1
await message.channel.send(
f'Removed {tag_or_altphr} `{val}` from "{q["title"]}"'
f'Removed tag `{val}` from "{q["title"]}"'
)
else: # clear
for q in questions:
if not q[field]:
await message.channel.send(
f'"{q["title"]}" already has no {tag_or_altphr}s'
f'"{q["title"]}" already has no tags'
)
else:
update_method(q, [])
n_edited += 1
await message.channel.send(
f'Cleared {tag_or_altphr}s on "{q["title"]}"'
f'Cleared tags on "{q["title"]}"'
)

if n_edited == 0:
response_text = "No questions were modified"
elif edit_action == "clear":
response_text = f"Cleared {tag_or_altphr}s on {n_edited} questions"
response_text = f"Cleared tags on {n_edited} questions"
else:
response_text = "Added" if edit_action == "add" else "Removed"
response_text += f" {tag_or_altphr} `{val}` {to_from_on} "
response_text += f" tag `{val}` {to_from_on} "
response_text += f"{n_edited} questions" if n_edited > 1 else "one question"

why = f"{message.author.display_name} asked me to {edit_action} "
if edit_action == "clear":
why += f"{tag_or_altphr}s"
elif tag_or_altphr == "tag":
why += "a tag"
why += "tags"
else:
why += "an alternate question"
why += "a tag"

return Response(confidence=10, text=response_text, why=why)

Expand Down Expand Up @@ -674,7 +618,6 @@ def test_cases(self) -> list[IntegrationTest]:
if ENVIRONMENT_TYPE != "development":
return []

test_altphr = "TEST_ALTERNATE_PHRASING"
return [
############
# Tags #
Expand Down Expand Up @@ -709,46 +652,6 @@ def test_cases(self) -> list[IntegrationTest]:
test_message="add tag open problem",
expected_regex="Added tag `Open Problem` to one question",
),
###########################
# Alternate phrasings #
###########################
self.create_integration_test(
test_message=f'alt "{test_altphr}" https://docs.google.com/document/d/1vg2kUNaMcQA2lB9zvJTn9npqVS-pkquLeODG7eVOyWE/edit',
expected_regex=f"Added alternate phrasing `{test_altphr}` to one question",
test_wait_time=1,
),
self.create_integration_test(
test_message=f'alt "{test_altphr}" https://docs.google.com/document/d/1vg2kUNaMcQA2lB9zvJTn9npqVS-pkquLeODG7eVOyWE/edit',
expected_regex="No questions were modified",
test_wait_time=1,
),
self.create_integration_test(
test_message=f'rm alt "{test_altphr}" from https://docs.google.com/document/d/1vg2kUNaMcQA2lB9zvJTn9npqVS-pkquLeODG7eVOyWE/edit',
expected_regex=f"Removed alternate phrasing `{test_altphr}` from one question",
test_wait_time=2,
),
self.create_integration_test(
test_message=f'rm alt "{test_altphr}" from https://docs.google.com/document/d/1vg2kUNaMcQA2lB9zvJTn9npqVS-pkquLeODG7eVOyWE/edit',
expected_regex="No questions were modified",
test_wait_time=2,
),
self.create_integration_test(
test_message="clear alt https://docs.google.com/document/d/1vg2kUNaMcQA2lB9zvJTn9npqVS-pkquLeODG7eVOyWE/edit",
expected_regex="No questions were modified",
test_wait_time=1,
),
self.create_integration_test(
test_message='add alt "XYZ" https://docs.google.com/document/d/1vg2kUNaMcQA2lB9zvJTn9npqVS-pkquLeODG7eVOyWE/edit https://docs.google.com/document/d/1KOHkRf1TCwB3x1OSUPOVKvUMvUDZPlOII4Ycrc0Aynk/edit',
expected_regex="I don't think you want",
),
self.create_integration_test(
test_message='rm alt "XYZ" https://docs.google.com/document/d/1vg2kUNaMcQA2lB9zvJTn9npqVS-pkquLeODG7eVOyWE/edit https://docs.google.com/document/d/1KOHkRf1TCwB3x1OSUPOVKvUMvUDZPlOII4Ycrc0Aynk/edit',
expected_regex="I don't think you want",
),
self.create_integration_test(
test_message="clear alt https://docs.google.com/document/d/1vg2kUNaMcQA2lB9zvJTn9npqVS-pkquLeODG7eVOyWE/edit https://docs.google.com/document/d/1KOHkRf1TCwB3x1OSUPOVKvUMvUDZPlOII4Ycrc0Aynk/edit",
expected_regex="No questions were modified",
),
##############
# Status #
##############
Expand Down
Loading