Dependencies
This commit is contained in:
parent
227fa3eaeb
commit
3e5b4cd7b8
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user