1
0

Dependencies

This commit is contained in:
Jon Michael Aanes 2024-09-28 14:10:13 +02:00
parent 227fa3eaeb
commit 3e5b4cd7b8
Signed by: Jmaa
SSH Key Fingerprint: SHA256:Ab0GfHGCblESJx7JRE4fj4bFy/KRpeLhi41y4pF3sNA
7 changed files with 77 additions and 15 deletions

View File

@ -17,13 +17,14 @@ Features:
- Description - Description
- Tags - Tags
- Assignees - Assignees
- Dependencies
- Change card features: - Change card features:
- Title - Title
- Description - Description
- [Obsidian](https://obsidian.md/) compatibility: - [Obsidian](https://obsidian.md/) compatibility:
- Mount within your vault. - Mountable within your vault.
- Link to cards by either card number or card title. - Link to cards by either card number or card title.
- Tags are directly integrated. - Tags and dependencies are integrated.
Limitations: Limitations:
@ -58,4 +59,5 @@ Following features are work in progress:
- [ ] Frontmatter: Dependencies. As vault links in Obsidian mode. - [ ] Frontmatter: Dependencies. As vault links in Obsidian mode.
- [ ] Allow users to toggle Obsidian mode, instead of being default. - [ ] Allow users to toggle Obsidian mode, instead of being default.
- [ ] Get the correct last-modified date. - [ ] Get the correct last-modified date.
- [ ] Improve cache behaviour. User and tags can have much longer cache times.
""" """

View File

@ -136,6 +136,10 @@ class FavroClient:
return card return card
return next(self.get_cards(seq_id=seq_id)) return next(self.get_cards(seq_id=seq_id))
def get_card_by_card_id(self, card_id: CardId) -> Card:
response = self.session.get(URL_UPDATE_CARD.format(card_id=card_id.raw_id))
return Card.from_json(response.json())
def get_user(self, user_id: UserId) -> UserInfo: def get_user(self, user_id: UserId) -> UserInfo:
response = self.session.get(URL_GET_USER.format(user_id=user_id.raw_id)) response = self.session.get(URL_GET_USER.format(user_id=user_id.raw_id))
return UserInfo.from_json(response.json()) return UserInfo.from_json(response.json())
@ -153,21 +157,22 @@ class FavroClient:
def update_card_contents_locally( def update_card_contents_locally(
self, card_id: CardId, card_contents: CardContents, self, card_id: CardId, card_contents: CardContents,
) -> Card: ) -> Card | None:
if card := self.cache.remove(card_id): if card := self.cache.remove(card_id):
card = dataclasses.replace( card = dataclasses.replace(
card, card,
detailed_description=card_contents.description, detailed_description=card_contents.description,
name=card_contents.name, name=card_contents.name,
tags=[], # TODO? tags=[], # TODO?
assignments=[], assignments=[], # TODO?
dependencies=[], # TODO
) )
self.cache.add_card(card) self.cache.add_card(card)
return card return card
def update_card_contents( def update_card_contents(
self, card_id: CardId, card_contents: CardContents, self, card_id: CardId, card_contents: CardContents,
) -> Card: ) -> Card | None:
"""Returns updated Card.""" """Returns updated Card."""
if self.read_only == 'silent': if self.read_only == 'silent':
logger.warning( logger.warning(

View File

@ -68,6 +68,11 @@ class TagId:
raw_id: str raw_id: str
@dataclasses.dataclass(frozen=True)
class CustomFieldId:
raw_id: str
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class TagInfo: class TagInfo:
tag_id: TagId tag_id: TagId
@ -85,6 +90,19 @@ class TagInfo:
) )
@dataclasses.dataclass(frozen=True)
class CustomField:
custom_field_id: CustomFieldId
value: list[str] # TODO
@staticmethod
def from_json(json: dict[str, Any]) -> 'CustomField':
return CustomField(
CustomFieldId(json['customFieldId']),
json['value'], # TODO
)
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class CardDependency: class CardDependency:
card_id: CardId card_id: CardId
@ -112,12 +130,13 @@ class Card:
name: str name: str
dependencies: list[CardDependency] dependencies: list[CardDependency]
assignments: list[CardAssignment] assignments: list[CardAssignment]
custom_fields: list[CustomField]
tags: list[TagId] tags: list[TagId]
todo_list_user_id: UserId | None todo_list_user_id: UserId | None
todo_list_completed: bool | None todo_list_completed: bool | None
creator_user_id: UserId creator_user_id: UserId
creation_date: datetime.datetime creation_date: datetime.datetime
attachments: list[dict]
detailed_description: str | None detailed_description: str | None
@staticmethod @staticmethod
@ -135,9 +154,11 @@ class Card:
name=json['name'], name=json['name'],
todo_list_user_id=todo_list_user_id, todo_list_user_id=todo_list_user_id,
todo_list_completed=json.get('todoListCompleted'), todo_list_completed=json.get('todoListCompleted'),
dependencies=json['dependencies'], dependencies=[CardDependency.from_json(dep) for dep in json['dependencies']],
tags=[TagId(tag) for tag in json['tags']], tags=[TagId(tag) for tag in json['tags']],
creator_user_id=UserId(json['createdByUserId']), creator_user_id=UserId(json['createdByUserId']),
creation_date=datetime.datetime.fromisoformat(json['createdAt']), creation_date=datetime.datetime.fromisoformat(json['createdAt']),
assignments=[CardAssignment.from_json(ass) for ass in json['assignments']], assignments=[CardAssignment.from_json(ass) for ass in json['assignments']],
custom_fields=[CustomField.from_json(field) for field in json['customFields']],
attachments=json['attachments'], # TODO
) )

View File

@ -168,11 +168,16 @@ class FavroFuse(fuse.Fuse):
self.favro_client.get_user(assignment.user).name self.favro_client.get_user(assignment.user).name
for assignment in card.assignments for assignment in card.assignments
] ]
dependencies = [
CARD_FILENAME_FORMAT.format(seq_id=self.favro_client.get_card_by_card_id(dep.card_id).seq_id.raw_id)
for dep in card.dependencies if dep.is_before
]
card_contents = CardContents( card_contents = CardContents(
card.name, card.name,
card.detailed_description, card.detailed_description,
tags, tags,
assignments, assignments,
dependencies,
) )
return self.formatter.format_card_contents(card_contents) return self.formatter.format_card_contents(card_contents)

View File

@ -12,6 +12,16 @@ class CardContents:
description: str | None description: str | None
tags: list[str] tags: list[str]
assignments: list[str] assignments: list[str]
card_dependencies: list[str]
def format_obsidian_link(text: str) -> str:
return f'"[[{text}]]"'
def parse_obsidian_link(text: str) -> str:
if m := re.match(r'^\[\[(.*)\]\]$', text):
return m.group(1)
return text
class CardFileFormatter: class CardFileFormatter:
@ -35,7 +45,13 @@ class CardFileFormatter:
frontmatter_data['assignments'] = card.assignments frontmatter_data['assignments'] = card.assignments
if self.obsidian_mode: if self.obsidian_mode:
frontmatter_data['assignments'] = [ frontmatter_data['assignments'] = [
f'"[[{name}]]"' for name in frontmatter_data['assignments'] format_obsidian_link(name) for name in frontmatter_data['assignments']
]
if card.card_dependencies:
frontmatter_data['dependencies'] = card.card_dependencies
if self.obsidian_mode:
frontmatter_data['dependencies'] = [
format_obsidian_link(name) for name in frontmatter_data['dependencies']
] ]
# Frontmatter # Frontmatter
@ -78,15 +94,19 @@ class CardFileFormatter:
document.children.remove(elem) document.children.remove(elem)
break break
tags: list[str] = fm.metadata.get('tags', [])
assignments: list[str] = fm.metadata.get('assignments', []) assignments: list[str] = fm.metadata.get('assignments', [])
for idx in range(0, len(assignments)): assignments = [parse_obsidian_link(text) for text in assignments]
if m := re.match(r'^\[\[(.*)\]\]$', assignments[idx]):
assignments[idx] = m.group(1) card_dependencies: list[str] = fm.metadata.get('dependencies', [])
card_dependencies=[parse_obsidian_link(text) for text in card_dependencies]
description = self.renderer.render_children(document).strip() description = self.renderer.render_children(document).strip()
return CardContents( return CardContents(
name, name,
description, description,
tags=fm.metadata.get('tags', []), tags=tags,
assignments=assignments, assignments=assignments,
card_dependencies=card_dependencies,
) )

View File

@ -1,5 +1,5 @@
from favro_sync import secrets from favro_sync import secrets
from favro_sync.favro_client import FavroClient, OrganizationId from favro_sync.favro_client import FavroClient, OrganizationId, SeqId
# TODO: Skip if no secrets # TODO: Skip if no secrets
@ -18,5 +18,12 @@ def test_create_client():
def test_get_card(): def test_get_card():
client = test_create_client() client = test_create_client()
card = next(client.get_todo_list_cards()) card = client.get_card(SeqId(11368))
print(card)
assert card is not None assert card is not None
assert card.name is not None
assert card.detailed_description is not None
assert len(card.dependencies) == 1
assert len(card.attachments) == 0
assert len(card.custom_fields) == 2

View File

@ -27,6 +27,8 @@ tags:
assignments: assignments:
- "[[Gunnar Gunnarson]]" - "[[Gunnar Gunnarson]]"
- "[[Alice Alicedottor]]" - "[[Alice Alicedottor]]"
dependencies:
- "[[PAR-9999]]"
--- ---
# Name of Card # Name of Card
@ -47,6 +49,6 @@ def test_parse_and_render_2():
card_contents = FORMATTER.parse_card_contents(EXAMPLE_TEXT_2) card_contents = FORMATTER.parse_card_contents(EXAMPLE_TEXT_2)
print(card_contents) print(card_contents)
assert card_contents.name == 'Respond to Carstens comments, and perform any relevant fixing' assert card_contents.name == 'Name of Card'
assert '---' not in card_contents.description assert '---' not in card_contents.description
assert FORMATTER.format_card_contents(card_contents) == EXAMPLE_TEXT_2 assert FORMATTER.format_card_contents(card_contents) == EXAMPLE_TEXT_2