From 8309ebae4ad69d868c7a781f1fb0ce42b889f4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Fri, 5 Jan 2024 17:54:27 +0100 Subject: [PATCH 1/4] fix(web): improve error message for group loading --- .../apps/mobile/src/navigation/Navigation.tsx | 9 +++++- .../TransactionList/TransactionList.tsx | 7 +++-- .../mobile/src/screens/groups/AccountList.tsx | 11 ++++---- frontend/apps/web/src/pages/groups/Group.tsx | 28 +++++++------------ 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/frontend/apps/mobile/src/navigation/Navigation.tsx b/frontend/apps/mobile/src/navigation/Navigation.tsx index cbca5b7..07a5578 100644 --- a/frontend/apps/mobile/src/navigation/Navigation.tsx +++ b/frontend/apps/mobile/src/navigation/Navigation.tsx @@ -1,4 +1,11 @@ -import { fetchGroupDependencies, selectGroups, selectIsAuthenticated, subscribe, unsubscribe } from "@abrechnung/redux"; +import { + fetchGroupDependencies, + selectGroupById, + selectGroups, + selectIsAuthenticated, + subscribe, + unsubscribe, +} from "@abrechnung/redux"; import { createDrawerNavigator } from "@react-navigation/drawer"; import { createMaterialTopTabNavigator } from "@react-navigation/material-top-tabs"; import { NavigationContainer } from "@react-navigation/native"; diff --git a/frontend/apps/mobile/src/screens/TransactionList/TransactionList.tsx b/frontend/apps/mobile/src/screens/TransactionList/TransactionList.tsx index b4ef387..3a05010 100644 --- a/frontend/apps/mobile/src/screens/TransactionList/TransactionList.tsx +++ b/frontend/apps/mobile/src/screens/TransactionList/TransactionList.tsx @@ -4,6 +4,7 @@ import { createTransaction, fetchTransactions, selectCurrentUserPermissions, + selectGroupById, selectGroupTransactionsStatus, selectSortedTransactions, } from "@abrechnung/redux"; @@ -20,6 +21,7 @@ import { useApi } from "../../core/ApiProvider"; import { GroupTabScreenProps } from "../../navigation/types"; import { selectActiveGroupId, + selectGroupSlice, selectTransactionSlice, selectUiSlice, useAppDispatch, @@ -35,6 +37,7 @@ export const TransactionList: React.FC = ({ navigation }) => { const { api } = useApi(); const groupId = useAppSelector((state) => selectActiveGroupId({ state: selectUiSlice(state) })) as number; // TODO: proper typing + const group = useAppSelector((state) => selectGroupById({ state: selectGroupSlice(state), groupId })); const [search, setSearch] = useState(""); const [sortMode, setSortMode] = useState("last_changed"); const transactions = useAppSelector((state) => @@ -79,7 +82,7 @@ export const TransactionList: React.FC = ({ navigation }) => { return; } navigation.getParent()?.setOptions({ - headerTitle: "Transactions", + headerTitle: group?.name ?? "", titleShown: !showSearchInput, headerRight: () => { if (showSearchInput) { @@ -118,7 +121,7 @@ export const TransactionList: React.FC = ({ navigation }) => { ); }, }); - }, [isFocused, showSearchInput, isMenuOpen, search, sortMode, theme, navigation]); + }, [group, isFocused, showSearchInput, isMenuOpen, search, sortMode, theme, navigation]); const createNewTransaction = (type: TransactionType) => { dispatch(createTransaction({ groupId, type })) diff --git a/frontend/apps/mobile/src/screens/groups/AccountList.tsx b/frontend/apps/mobile/src/screens/groups/AccountList.tsx index 6cc7180..ece47dc 100644 --- a/frontend/apps/mobile/src/screens/groups/AccountList.tsx +++ b/frontend/apps/mobile/src/screens/groups/AccountList.tsx @@ -6,7 +6,7 @@ import { selectAccountBalances, selectCurrentUserPermissions, selectGroupAccountsStatus, - selectGroupCurrencySymbol, + selectGroupById, selectSortedAccounts, } from "@abrechnung/redux"; import { Account, AccountBalance } from "@abrechnung/types"; @@ -40,6 +40,7 @@ export const AccountList: React.FC = ({ route, navigation }) => { const accountType: AccountType = route.name === "AccountList" ? "personal" : "clearing"; const groupId = useAppSelector((state) => selectActiveGroupId({ state: selectUiSlice(state) })) as number; // TODO: proper typing + const group = useAppSelector((state) => selectGroupById({ state: selectGroupSlice(state), groupId })); const [search, setSearch] = useState(""); const [sortMode, setSortMode] = useState("name"); const accounts = useAppSelector((state) => @@ -53,9 +54,7 @@ export const AccountList: React.FC = ({ route, navigation }) => { ); const accountBalances = useAppSelector((state) => selectAccountBalances({ state, groupId })); const permissions = useAppSelector((state) => selectCurrentUserPermissions({ state: state, groupId })); - const currency_symbol = useAppSelector((state) => - selectGroupCurrencySymbol({ state: selectGroupSlice(state), groupId }) - ); + const currency_symbol = group?.currency_symbol; const accountStatus = useAppSelector((state) => selectGroupAccountsStatus({ state: selectAccountSlice(state), groupId }) ); @@ -87,7 +86,7 @@ export const AccountList: React.FC = ({ route, navigation }) => { } navigation.getParent()?.setOptions({ - headerTitle: accountType === "personal" ? "People" : "Events", + headerTitle: group?.name ?? "", titleShown: !showSearchInput, headerRight: () => { if (showSearchInput) { @@ -125,7 +124,7 @@ export const AccountList: React.FC = ({ route, navigation }) => { ); }, }); - }, [isFocused, showSearchInput, isMenuOpen, setMenuOpen, sortMode, theme, navigation, accountType, search]); + }, [group, isFocused, showSearchInput, isMenuOpen, setMenuOpen, sortMode, theme, navigation, accountType, search]); const createNewAccount = () => { dispatch( diff --git a/frontend/apps/web/src/pages/groups/Group.tsx b/frontend/apps/web/src/pages/groups/Group.tsx index 10b4bfa..1bfdbd3 100644 --- a/frontend/apps/web/src/pages/groups/Group.tsx +++ b/frontend/apps/web/src/pages/groups/Group.tsx @@ -9,19 +9,12 @@ import { unsubscribe, } from "@abrechnung/redux"; import React, { Suspense } from "react"; -import { batch } from "react-redux"; import { Navigate, Route, Routes, useParams } from "react-router-dom"; import { toast } from "react-toastify"; import { Balances } from "../accounts/Balances"; -import { Loading } from "../../components/style/Loading"; -import { api, ws } from "../../core/api"; -import { - selectAccountSlice, - selectGroupSlice, - selectTransactionSlice, - useAppDispatch, - useAppSelector, -} from "../../store"; +import { Loading } from "@/components/style/Loading"; +import { api, ws } from "@/core/api"; +import { selectAccountSlice, selectGroupSlice, selectTransactionSlice, useAppDispatch, useAppSelector } from "@/store"; import { AccountDetail } from "../accounts/AccountDetail"; import { PersonalAccountList } from "../accounts/PersonalAccountList"; import { ClearingAccountList } from "../accounts/ClearingAccountList"; @@ -32,6 +25,7 @@ import { GroupLog } from "./GroupLog"; import { GroupMemberList } from "./GroupMemberList"; import { GroupSettings } from "./GroupSettings"; import { TransactionDetail } from "../transactions/TransactionDetail"; +import { SerializedError } from "@reduxjs/toolkit"; export const Group: React.FC = () => { const params = useParams(); @@ -69,14 +63,12 @@ export const Group: React.FC = () => { React.useEffect(() => { if (groupExists) { - batch(() => { - dispatch(fetchGroupDependencies({ groupId, api, fetchAnyway: true })) - .unwrap() - .catch((err) => { - console.warn(err); - toast.error(`Error while loading transactions and accounts: ${err}`); - }); - }); + dispatch(fetchGroupDependencies({ groupId, api, fetchAnyway: true })) + .unwrap() + .catch((err: SerializedError) => { + console.warn(err); + toast.error(`Error while loading transactions and accounts: ${err.message}`); + }); } }, [groupExists, groupId, dispatch]); From 7a839a46124cd861a2c0771d295df6c56007f631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Fri, 5 Jan 2024 17:54:34 +0100 Subject: [PATCH 2/4] chore: add changelog --- CHANGELOG.md | 13 ++++++ README.md | 40 ++++++++-------- pyproject.toml | 2 + tools/make_release.py | 104 +++++++++++++++++++++++++++++++++--------- 4 files changed, 117 insertions(+), 42 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..016eb35 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +## Unreleased + +[Compare the full difference.](https://github.com/SFTtech/abrechnung/compare/v0.12.1...HEAD) + +## 0.12.1 (2024-01-05) + +[Compare the full difference.](https://github.com/SFTtech/abrechnung/compare/v0.12.0...v0.12.1) + +### Fixed + +- Correctly filter out deleted transactions in balance computations \ No newline at end of file diff --git a/README.md b/README.md index dc22f01..9786fcf 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,18 @@ The *Abrechnung* (German for *reckoning*, *settlement*, *revenge*) aims to be a versatile and user-centric **payment**, **transaction** and **bookkeeping** management tool for human groups and events. ->>> You can simply **try** our [**demo instance**](https://demo.abrechnung.sft.lol)! +> You can simply **try** our [**demo instance**](https://demo.abrechnung.sft.lol)! +> You can get the android app over from the [releases page](https://github.com/SFTtech/abrechnung/releases/latest) Abrechnung is a tool to track *money*, *purchases* (and its items) and *debtors* for: -Group life | Events | Travelling --|-|- -Flat share roommates | Cooking revelries | Holiday trips -Your Hackerspace | LAN parties | Business trips -Family life | Regular parties | Adventures -... | ... | ... - +| Group life | Events | Travelling | +|----------------------|-------------------|----------------| +| Flat share roommates | Cooking revelries | Holiday trips | +| Your Hackerspace | LAN parties | Business trips | +| Family life | Regular parties | Adventures | +| ... | ... | ... | --- @@ -38,13 +38,12 @@ To help you set up your instance or understand the inner workings: ## Technical foundation -Technology | Component -------------------|---------- -**Python** | Backend logic -**React** | Web UI framework -**PostgresSQL** | Database -**Homo Sapiens** | Magic sauce - +| Technology | Component | +|------------------|------------------| +| **Python** | Backend logic | +| **React** | Web UI framework | +| **PostgresSQL** | Database | +| **Homo Sapiens** | Magic sauce | ## Contributing @@ -61,12 +60,11 @@ If there is **that feature** you really want to see implemented, you found a **b To directly reach developers and other users, we have chat rooms. For questions, suggestions, problem support, please join and just ask! -Contact | Where? ------------------|------- -Issue Tracker | [SFTtech/abrechnung](https://github.com/SFTtech/abrechnung/issues) -Matrix Chat | [`#sfttech:matrix.org`](https://app.element.io/#/room/#sfttech:matrix.org) -Support us | [![money sink](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/SFTtech) - +| Contact | Where? | +|---------------|-------------------------------------------------------------------------------------------------| +| Issue Tracker | [SFTtech/abrechnung](https://github.com/SFTtech/abrechnung/issues) | +| Matrix Chat | [`#sfttech:matrix.org`](https://app.element.io/#/room/#sfttech:matrix.org) | +| Support us | [![money sink](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/SFTtech) | ## License diff --git a/pyproject.toml b/pyproject.toml index 4dc0b0a..683b5d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,4 +111,6 @@ commit = false files = [ { filename = "abrechnung/__init__.py" }, { filename = "frontend/apps/mobile/android/app/build.gradle" }, + { filename = "CHANGELOG.md", search = "Unreleased", replace = "{current_version} ({now:%Y-%m-%d})"}, + { filename = "CHANGELOG.md", search = "v{current_version}...HEAD", replace = "v{current_version}...v{new_version}"}, ] diff --git a/tools/make_release.py b/tools/make_release.py index dad84c9..1cd5e62 100755 --- a/tools/make_release.py +++ b/tools/make_release.py @@ -1,11 +1,32 @@ import argparse -import json import re import subprocess +import tomllib +from datetime import datetime from pathlib import Path +from pydantic import BaseModel + repo_root = Path(__file__).parent.parent +debian_changelog_template = """abrechnung ({new_version}) stable; urgency=medium + + * Abrechnung release {new_version} + + -- {author_name} <{author_email}> {now:%a, %-d %b %Y %H:%M:%S %z} + +""" +unreleased_changelog_template = """## Unreleased + +[Compare the full difference.](https://github.com/SFTtech/abrechnung/compare/v{new_version}...HEAD) + +""" + + +class Config(BaseModel): + current_version: str + new_version: str + def parse_args(): parser = argparse.ArgumentParser("Abrechnung release utility") @@ -15,26 +36,19 @@ def parse_args(): return parser.parse_args() -def _get_next_version(part: str) -> tuple[str, str]: +def _get_bumpversion_config(part: str) -> Config: ret = subprocess.run( ["bump-my-version", "show", "--format", "json", "--increment", part], capture_output=True, check=True ) - result = json.loads(ret.stdout) - return result["current_version"], result["new_version"] + return Config.model_validate_json(ret.stdout) -def main(part: str, dry_run: bool): - if dry_run: - print("Performing a dry run ...") - # print current then prompt for new API compatibility version ranges - current_version, next_version = _get_next_version(part) - print(f"Current Version: {current_version}, Upgrading to version {next_version}") +def _read_pyproject_toml(): + with open(repo_root / "pyproject.toml", "rb") as f: + return tomllib.load(f) - bump_my_version_args = ["bump-my-version", "bump", part, "--no-commit", "--no-tag"] - if dry_run: - bump_my_version_args.append("--dry-run") - subprocess.run(bump_my_version_args, check=True) +def _update_app_gradle(dry_run: bool): app_build_gradle = repo_root / "frontend" / "apps" / "mobile" / "android" / "app" / "build.gradle" gradle_content = app_build_gradle.read_text() if not dry_run: @@ -44,14 +58,62 @@ def main(part: str, dry_run: bool): gradle_content = gradle_content.replace(f"versionCode {code}", f"versionCode {code + 1}") app_build_gradle.write_text(gradle_content) - print("Do not forget to update the api version compatibilities") - print("Do not forget to add a debian changelog entry") - # generated changelog from commits / merges / whatever - # print current changelog - # prompt for additional changelog entries - # finalize changelog and write - # copy changelog to debian changelog +def _update_debian_changelog(pyproject: dict, config: Config, dry_run: bool): + formatted_debian_changelog = debian_changelog_template.format( + new_version=config.new_version, + now=datetime.now(), + author_name=pyproject["project"]["authors"][0]["name"], + author_email=pyproject["project"]["authors"][0]["email"], + ) + print("Adding release entry to debian changelog") + deb_changelog_path = repo_root / "debian" / "changelog" + current_deb_changelog = deb_changelog_path.read_text() + new_changelog = formatted_debian_changelog + current_deb_changelog + if not dry_run: + deb_changelog_path.write_text(new_changelog, "utf-8") + + +def _update_changelog(config: Config, dry_run: bool): + # re-add the unreleased header to the changelog + formatted_unreleased_changelog = unreleased_changelog_template.format( + new_version=config.new_version, + ) + changelog_path = repo_root / "CHANGELOG.md" + current_changelog = changelog_path.read_text() + first_line_end = current_changelog.find("\n\n") + new_changelog = ( + current_changelog[: first_line_end + 2] + + formatted_unreleased_changelog + + current_changelog[first_line_end + 2 :] + ) + print("Adding unreleased section to CHANGELOG.md") + if not dry_run: + changelog_path.write_text( + new_changelog, + "utf-8", + ) + + +def main(part: str, dry_run: bool): + if dry_run: + print("Performing a dry run ...") + + # print current then prompt for new API compatibility version ranges + config = _get_bumpversion_config(part) + pyproject = _read_pyproject_toml() + print(f"Current Version: {config.current_version}, Upgrading to version {config.new_version}") + + bump_my_version_args = ["bump-my-version", "bump", part, "--no-commit", "--no-tag"] + if dry_run: + bump_my_version_args.append("--dry-run") + subprocess.run(bump_my_version_args, check=True) + + _update_app_gradle(dry_run) + _update_debian_changelog(pyproject, config, dry_run) + _update_changelog(config, dry_run) + + print("Do not forget to update the api version compatibilities") if __name__ == "__main__": From 5fc689db36cfeae41c1a47f0fa5d8e0a4252adc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Fri, 5 Jan 2024 18:39:12 +0100 Subject: [PATCH 3/4] chore: add makefile job for docs serving --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 54feb29..ee15212 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,10 @@ package: docs: $(MAKE) -C docs html +.PHONY: serve-docs +serve-docs: + python3 -m http.server -d docs/_build/html 8888 + .PHONY: generate-openapi generate-openapi: mkdir -p api From 25ce9823bd9e84eaa545bd890802244c2bd0938d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Fri, 5 Jan 2024 19:56:02 +0100 Subject: [PATCH 4/4] fix: lint errors --- .github/workflows/backend.yaml | 12 ++++++------ frontend/apps/mobile/src/navigation/Navigation.tsx | 9 +-------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/.github/workflows/backend.yaml b/.github/workflows/backend.yaml index 46771d3..12148aa 100644 --- a/.github/workflows/backend.yaml +++ b/.github/workflows/backend.yaml @@ -7,10 +7,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Python 3.10 + - name: Set up Python 3.11 uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.11" - name: Install dependencies run: pip install -e '.[dev,test]' - name: Analysing the code with pylint @@ -20,10 +20,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Python 3.10 + - name: Set up Python 3.11 uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.11" - name: Install dependencies run: pip install -e '.[dev,test]' - name: Analysing the code with mypy @@ -71,10 +71,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Python 3.10 + - name: Set up Python 3.11 uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.11" - name: Install dependencies run: pip install -e '.[dev,test]' - name: Check formatting diff --git a/frontend/apps/mobile/src/navigation/Navigation.tsx b/frontend/apps/mobile/src/navigation/Navigation.tsx index 07a5578..cbca5b7 100644 --- a/frontend/apps/mobile/src/navigation/Navigation.tsx +++ b/frontend/apps/mobile/src/navigation/Navigation.tsx @@ -1,11 +1,4 @@ -import { - fetchGroupDependencies, - selectGroupById, - selectGroups, - selectIsAuthenticated, - subscribe, - unsubscribe, -} from "@abrechnung/redux"; +import { fetchGroupDependencies, selectGroups, selectIsAuthenticated, subscribe, unsubscribe } from "@abrechnung/redux"; import { createDrawerNavigator } from "@react-navigation/drawer"; import { createMaterialTopTabNavigator } from "@react-navigation/material-top-tabs"; import { NavigationContainer } from "@react-navigation/native";