{"id":20631051,"url":"https://github.com/gfarrell/grasana","last_synced_at":"2026-04-21T14:32:44.374Z","repository":{"id":142037735,"uuid":"389107687","full_name":"gfarrell/grasana","owner":"gfarrell","description":"Represent Asana projects as graphs (well, trees mostly)","archived":false,"fork":false,"pushed_at":"2021-07-25T14:22:09.000Z","size":186,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"trunk","last_synced_at":"2025-04-21T09:06:30.370Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gfarrell.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-07-24T13:39:02.000Z","updated_at":"2024-09-11T09:22:43.000Z","dependencies_parsed_at":null,"dependency_job_id":"1a9cd203-4e40-439f-ab9d-ca103c0c4670","html_url":"https://github.com/gfarrell/grasana","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gfarrell/grasana","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gfarrell%2Fgrasana","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gfarrell%2Fgrasana/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gfarrell%2Fgrasana/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gfarrell%2Fgrasana/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gfarrell","download_url":"https://codeload.github.com/gfarrell/grasana/tar.gz/refs/heads/trunk","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gfarrell%2Fgrasana/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32095858,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-21T11:25:29.218Z","status":"ssl_error","status_checked_at":"2026-04-21T11:25:28.499Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-16T14:10:41.821Z","updated_at":"2026-04-21T14:32:44.350Z","avatar_url":"https://github.com/gfarrell.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Grasana\n\nGrasana is a tool for representing Asana projects as graphs (well, mostly as\ntrees actually). It can output the following formats:\n\n- DOT: the graphviz DOT language\n- HTML: an HTML page complete with D3 to render the graph nicely and\n  interactively\n- JSON: representing either in graph form or tree form in JSON\n\nThere are two representations Grasana uses for projects: \"graph\" form and \"tree\"\nform. The graph form represents the project as a tuple containing a list of\ntasks and a list of edges (relations between tasks). Edges can represent either\nsubtask relations or dependencies (NB. Grasana does not currently parse\ndependencies, only subtasks). The tree form represents the project as a tree in\nwhich each node has an `id`, a `name`, and `children` (a list of child nodes).\n\nThe `DOT` output format uses the graph representation, whereas the `HTML` format\ntechnically uses the tree representation under the hood. You can output either\ngraph or tree form when using the `JSON` format\n\n## Building and installing\n\nThere are two parts of Grasana: a typescript part and a Haskell part. The\ntypescript part is for the HTML output to make a nice tree viewer.\n\n### Dependencies\n\nIn order to build this project you will need to install:\n\n- [stack](haskellstack.org)\n- [ghc](https://www.haskell.org/ghc/) \u003e= 8.10\n- [nodejs](nodejs.org/)\n- [yarn](classic.yarnpkg.com) (v1)\n\n### Building\n\nYou can build the `grasana` executable by running `make dist/grasana`.\nIf it already exists, run `make clean` first. The executable will then\nbe copied, somewhat unsurprisingly, to `dist/grasana`.\n\n### Installing\n\nIf you want to install the binary you can run `make install` which will\nclean, build, and then copy the binary to your local binary path (you\ncan find out what this is by running `stack path --local-bin`, the\ndefault is `$HOME/.local/bin`). You will now be able to run `grasana` from\nwherever you like!\n\n## Usage\n\n    grasana format [-t token] projectid\n\n* `format`: can be one of `html`, `dot`, `jsontree`, or `jsongraph`;\n* `token`: your [Asana PAT][asana-pat] (optionally as an environment variable\n  instead to keep it out of your command history);\n* `projectid`: the id of the project you want to represent graphically.\n\n[asana-pat]: https://developers.asana.com/docs/personal-access-token\n\n### Example: using environment variables\n\nTo avoid having to pass your Asana personal access token explicitly on\nthe command line you can set it in your shell's environment, for example\nif you had a file called `secrets.env` with the following contents, you\ncould run `. ./secrets.env \u0026\u0026 grasana format projectid`.\n\n    ASANA_PAT=\"\u003cyour PAT\u003e\"\n\n### Example: rendering immediately with graphviz\n\nIf you have [graphviz](https://graphviz.org/) installed, you can use it to\nimmediately render your project as an SVG.\n\n    grasana dot \u003cprojectid\u003e | dot -Tsvg \u003e ./project.svg\n\n## Testing\n\nSome (but not all) of grasana has unit tests, just run `stack test` to go\nthrough the spec. All tests specs are written using `Hspec` and can be found in\nthe `/test` directory.\n\n    stack test\n\n### Mocking HTTP Requests\n\nI haven't yet worked out a good way to do this in Haskell so everything which\ntouches the Asana API via HTTP requests lacks unit tests. I have some ideas,\nhowever, inspired by the following resources:\n\n- https://making.pusher.com/unit-testing-io-in-haskell/\n- https://lexi-lambda.github.io/blog/2017/06/29/unit-testing-effectful-haskell-with-monad-mock/\n\nThis might look something along the lines of a specialised Monad for HTTP:\n\n    class Monad m =\u003e MonadHTTP m where\n      httpJSON :: Network.HTTP.Simple.Request -\u003e m (Network.HTTP.Simple.Response Data.ByteString.Lazy.ByteString)\n\n## Contributing\n\nGrasana was a toy project because I was frustrated by how bad Asana is at\nhandling deeply nested projects (which seems to me to be a good way of\nrepresenting [outcome maps][outcome-maps]).\n\nIf you would like to help make Grasana better, here is my TODO list which I'm\nsure is missing many things. I have not been as good as I would have liked at\nadding tests for Grasana (as you can see above) but please do add them where\npossible when you are submitting features or fixes. Fork the repo, make some\nchanges in a branch, push it up and open a PR explaining the changes and the\nreasoning behind the implementation and I'll review and merge (or reject).\n\n[outcome-maps]: http://www.aaronsw.com/weblog/theoryofchange\n\n### TODO\n\n- [x] Asana API interactions\n- [x] Graph representation\n- [x] Tree representation\n- [x] DOT output\n- [x] Incorporate built JS for HTML output\n- [x] Update JS app to handle incl. json\n- [ ] Write tests for DOT rendering\n- [ ] Write tests for HTML rendering\n- [ ] Handle unsound graphs and exit properly\n- [ ] Handle Asana errors nicely (auth, 404, etc.)\n- [x] Handle missing PAT nicely\n- [ ] Find a way to mock HTTP requests\n- [x] Add installation instructions\n- [x] Add contribution instructions\n- [x] Allow passing of asana token as an option\n- [ ] Zoom to mouse point not origin in `InteractiveSVGViewer`\n- [ ] Add bounds around labels in the HTML visualisation\n- [ ] Wrap text labels in the HTML visualisation\n\n### Developing the HTML viewer\n\nIn order to make development of the HTML viewer easier, you can run a\nlocal webpack server with live-reloading of changes. Just work inside\nthe `js` directory and run `yarn serve`. This will fire up a development\nserver on `localhost:8080` which you can use to test your changes.\n\nNote that the application expects a JSON representation of the project\nin tree form to be accessible at `window.treejson`. The `index.html` has\na dummy project copied in there for convenience of development, but if\nyou want to test something specific you can replace it with a project of\nyour choice. The easiest way to do this is to get Grasana to generate\nthe JSON of an actual project using `grasana jsontree -t \u003cyour token\u003e\n\u003cyour project id\u003e` which you can then copy to your clipboard (e.g.\npiping into `xclip -selection clipboard`) and pasting into `index.html`.\n\n### Developing the CLI\n\nStack's `run` command is very useful for running the programme as you\ndevelop. The project is organised into some representations (`TaskGraph`\nand `TaskTree` modules) and renderers (`Html` and `Dot`). The `Asana`\nmodule contains the functions for actually tasking to the Asana API.\n\nIn order to include the typescript project output into the generated\nHTML, Grasana uses Template Haskell via `blaze-html` and `shakespeare`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgfarrell%2Fgrasana","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgfarrell%2Fgrasana","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgfarrell%2Fgrasana/lists"}