From 299015877a52dcd3e6d32f32323d322c5570386a Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Fri, 19 Jan 2024 11:46:01 -0700 Subject: [PATCH] allow ghc 9.6 (#4) * add nix dev shell with ghc 9.6 * change formatting to fourmolu * upgrade github actions * update stack.yaml to lts 22 * add role annotations for ghc 9.8 --- .github/workflows/ci.yml | 16 +- .restyled.yaml | 10 + .stylish-haskell.yaml | 25 --- CHANGELOG.md | 6 +- asana.cabal | 5 +- brittany.yaml | 70 ------- flake.lock | 234 +++++++++++++++++++++ flake.nix | 81 ++++++++ fourmolu.yaml | 15 ++ library/Asana/Api/CustomField.hs | 72 +++---- library/Asana/Api/Gid.hs | 23 ++- library/Asana/Api/Named.hs | 12 +- library/Asana/Api/Prelude.hs | 26 ++- library/Asana/Api/Project.hs | 14 +- library/Asana/Api/Request.hs | 338 ++++++++++++++++--------------- library/Asana/Api/Tag.hs | 12 +- library/Asana/Api/Task.hs | 166 +++++++-------- library/Asana/Api/Task/Search.hs | 68 +++---- package.yaml | 3 +- stack-lts-19.6.yaml | 1 + stack-lts-19.6.yaml.lock | 12 ++ stack-lts-20.26.yaml | 1 + stack-lts-20.26.yaml.lock | 12 ++ stack-lts-21.25.yaml | 1 + stack-lts-21.25.yaml.lock | 12 ++ stack-nightly.yaml | 2 +- stack-nightly.yaml.lock | 8 +- stack.yaml | 2 +- stack.yaml.lock | 8 +- 29 files changed, 789 insertions(+), 466 deletions(-) create mode 100644 .restyled.yaml delete mode 100644 .stylish-haskell.yaml delete mode 100644 brittany.yaml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 fourmolu.yaml create mode 100644 stack-lts-19.6.yaml create mode 100644 stack-lts-19.6.yaml.lock create mode 100644 stack-lts-20.26.yaml create mode 100644 stack-lts-20.26.yaml.lock create mode 100644 stack-lts-21.25.yaml create mode 100644 stack-lts-21.25.yaml.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d238e62..cbf9d3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,8 +12,11 @@ jobs: strategy: matrix: stack-yaml: - - stack-nightly.yaml # ghc-9.2 - - stack.yaml # ghc-9.0 + - stack-nightly.yaml # ghc-9.8 + - stack.yaml # ghc-9.6 + - stack-lts-21.25.yaml # ghc-9.4 + - stack-lts-20.26.yaml # ghc-9.2 + - stack-lts-19.6.yaml # ghc-9.0 - stack-lts-18.28.yaml # ghc-8.10 - stack-lts-16.31.yaml # ghc-8.8 - stack-lts-14.27.yaml # ghc-8.6 @@ -21,18 +24,15 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v3 - - uses: freckle/stack-cache-action@v2 - with: - stack-yaml: ${{ matrix.stack-yaml }} - - uses: freckle/stack-action@v3 + - uses: actions/checkout@v4 + - uses: freckle/stack-action@v4 with: stack-yaml: ${{ matrix.stack-yaml }} lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: rwe/actions-hlint-setup@v1 - uses: rwe/actions-hlint-run@v2 with: diff --git a/.restyled.yaml b/.restyled.yaml new file mode 100644 index 0000000..effe48b --- /dev/null +++ b/.restyled.yaml @@ -0,0 +1,10 @@ +restylers: + - fourmolu: + image: restyled/restyler-fourmolu:v0.13.0.0 + include: + - "/**/*.hs" + +request_review: author + +labels: + - restyled diff --git a/.stylish-haskell.yaml b/.stylish-haskell.yaml deleted file mode 100644 index 627f80a..0000000 --- a/.stylish-haskell.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- -steps: - - simple_align: - cases: false - top_level_patterns: false - records: false - - imports: - align: none - list_align: after_alias - pad_module_names: false - long_list_align: new_line_multiline - empty_list_align: right_after - list_padding: 2 - separate_lists: false - space_surround: false - - language_pragmas: - style: vertical - align: false - remove_redundant: false - - trailing_whitespace: {} -columns: 80 -newline: native -# For multi-package repositories this default-extensions must be set manually. -# For single package repos it can be inferred from the cabal file. -cabal: true diff --git a/CHANGELOG.md b/CHANGELOG.md index dda0fa0..9544b16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -## [_Unreleased_](https://github.com/freckle/asana-hs/compare/v1.0.1.0...main) +## [_Unreleased_](https://github.com/freckle/asana-hs/compare/v1.0.1.1...main) + +## [v1.0.1.1](https://github.com/freckle/asana-hs/compare/v1.0.1.0...v1.0.1.1) + +- Add support for GHC 9.6 ## [v1.0.1.0](https://github.com/freckle/asana-hs/compare/v1.0.0.0...v1.0.1.0) diff --git a/asana.cabal b/asana.cabal index 78198ab..ea761ea 100644 --- a/asana.cabal +++ b/asana.cabal @@ -1,11 +1,11 @@ cabal-version: 1.18 --- This file has been generated from package.yaml by hpack version 0.35.1. +-- This file has been generated from package.yaml by hpack version 0.36.0. -- -- see: https://github.com/sol/hpack name: asana -version: 1.0.1.0 +version: 1.0.1.1 synopsis: Asana API Client description: Please see README.md category: Utils @@ -61,6 +61,7 @@ library OverloadedStrings RankNTypes RecordWildCards + RoleAnnotations ScopedTypeVariables StandaloneDeriving TypeApplications diff --git a/brittany.yaml b/brittany.yaml deleted file mode 100644 index 086c336..0000000 --- a/brittany.yaml +++ /dev/null @@ -1,70 +0,0 @@ ---- -conf_debug: - dconf_roundtrip_exactprint_only: false - dconf_dump_bridoc_simpl_par: false - dconf_dump_ast_unknown: false - dconf_dump_bridoc_simpl_floating: false - dconf_dump_config: false - dconf_dump_bridoc_raw: false - dconf_dump_bridoc_final: false - dconf_dump_bridoc_simpl_alt: false - dconf_dump_bridoc_simpl_indent: false - dconf_dump_annotations: false - dconf_dump_bridoc_simpl_columns: false - dconf_dump_ast_full: false -conf_forward: - options_ghc: - - -XBangPatterns - - -XDataKinds - - -XDeriveAnyClass - - -XDeriveFoldable - - -XDeriveFunctor - - -XDeriveGeneric - - -XDeriveLift - - -XDeriveTraversable - - -XDerivingStrategies - - -XFlexibleContexts - - -XFlexibleInstances - - -XGADTs - - -XGeneralizedNewtypeDeriving - - -XLambdaCase - - -XMultiParamTypeClasses - - -XNoImplicitPrelude - - -XNoMonomorphismRestriction - - -XOverloadedStrings - - -XRankNTypes - - -XRecordWildCards - - -XScopedTypeVariables - - -XStandaloneDeriving - - -XTypeApplications - - -XTypeFamilies -conf_errorHandling: - econf_ExactPrintFallback: ExactPrintFallbackModeInline - econf_Werror: false - econf_omit_output_valid_check: false - econf_produceOutputOnErrors: false -conf_preprocessor: - ppconf_CPPMode: CPPModeAbort - ppconf_hackAroundIncludes: false -conf_obfuscate: false -conf_roundtrip_exactprint_only: false -conf_version: 1 -conf_layout: - lconfig_reformatModulePreamble: true - lconfig_altChooser: - tag: AltChooserBoundedSearch - contents: 3 - lconfig_allowSingleLineExportList: false - lconfig_importColumn: 60 - lconfig_hangingTypeSignature: false - lconfig_importAsColumn: 50 - lconfig_alignmentLimit: 1 - lconfig_indentListSpecial: true - lconfig_indentAmount: 2 - lconfig_alignmentBreakOnMultiline: true - lconfig_cols: 80 - lconfig_indentPolicy: IndentPolicyLeft - lconfig_indentWhereSpecial: true - lconfig_columnAlignMode: - tag: ColumnAlignModeDisabled - contents: 0.7 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ea2784e --- /dev/null +++ b/flake.lock @@ -0,0 +1,234 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "freckle": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs-23-05": "nixpkgs-23-05", + "nixpkgs-master-2023-05-06": "nixpkgs-master-2023-05-06", + "nixpkgs-master-2023-07-18": "nixpkgs-master-2023-07-18", + "nixpkgs-master-2023-09-15": "nixpkgs-master-2023-09-15", + "nixpkgs-stable": "nixpkgs-stable", + "nixpkgs-stable-2023-07-25": "nixpkgs-stable-2023-07-25", + "nixpkgs-unstable-2023-10-21": "nixpkgs-unstable-2023-10-21" + }, + "locked": { + "dir": "main", + "lastModified": 1705043131, + "narHash": "sha256-Hnf2wzvXBKznYEll8JILCQv4uAKfWTWU7jberWKE8RE=", + "owner": "freckle", + "repo": "flakes", + "rev": "9b0b7889579451fa3f45bf43dedd35d8c55e2617", + "type": "github" + }, + "original": { + "dir": "main", + "owner": "freckle", + "repo": "flakes", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1705458851, + "narHash": "sha256-uQvEhiv33Zj/Pv364dTvnpPwFSptRZgVedDzoM+HqVg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "8bf65f17d8070a0a490daf5f1c784b87ee73982c", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-23-05": { + "locked": { + "lastModified": 1701362232, + "narHash": "sha256-GVdzxL0lhEadqs3hfRLuj+L1OJFGiL/L7gCcelgBlsw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "d2332963662edffacfddfad59ff4f709dde80ffe", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-master-2023-05-06": { + "locked": { + "lastModified": 1683392273, + "narHash": "sha256-pZTuxvcuDeBG+vvE1zczNyEUzlPbzXVh8Ed45Fzo+tQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "16b3b0c53b1ee8936739f8c588544e7fcec3fc60", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "16b3b0c53b1ee8936739f8c588544e7fcec3fc60", + "type": "github" + } + }, + "nixpkgs-master-2023-07-18": { + "locked": { + "lastModified": 1689680872, + "narHash": "sha256-brNix2+ihJSzCiKwLafbyejrHJZUP0Fy6z5+xMOC27M=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "08700de174bc6235043cb4263b643b721d936bdb", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "08700de174bc6235043cb4263b643b721d936bdb", + "type": "github" + } + }, + "nixpkgs-master-2023-09-15": { + "locked": { + "lastModified": 1694760568, + "narHash": "sha256-3G07BiXrp2YQKxdcdms22MUx6spc6A++MSePtatCYuI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "46688f8eb5cd6f1298d873d4d2b9cf245e09e88e", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "46688f8eb5cd6f1298d873d4d2b9cf245e09e88e", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1701263465, + "narHash": "sha256-lNXUIlkfyDyp9Ox21hr+wsEf/IBklLvb6bYcyeXbdRc=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "50aa30a13c4ab5e7ba282da460a3e3d44e9d0eb3", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable-2023-07-25": { + "locked": { + "lastModified": 1690271650, + "narHash": "sha256-qwdsW8DBY1qH+9luliIH7VzgwvL+ZGI3LZWC0LTiDMI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "6dc93f0daec55ee2f441da385aaf143863e3d671", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "6dc93f0daec55ee2f441da385aaf143863e3d671", + "type": "github" + } + }, + "nixpkgs-unstable-2023-10-21": { + "locked": { + "lastModified": 1697793076, + "narHash": "sha256-02e7sCuqLtkyRgrZmdOyvAcQTQdcXj+vpyp9bca6cY4=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "038b2922be3fc096e1d456f93f7d0f4090628729", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "038b2922be3fc096e1d456f93f7d0f4090628729", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "freckle": "freckle", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..11a6921 --- /dev/null +++ b/flake.nix @@ -0,0 +1,81 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11"; + freckle.url = "github:freckle/flakes?dir=main"; + flake-utils.url = "github:numtide/flake-utils"; + }; + outputs = inputs: inputs.flake-utils.lib.eachDefaultSystem (system: + let + nixpkgsArgs = { inherit system; config = { }; }; + nixpkgs = import inputs.nixpkgs nixpkgsArgs; + freckle = inputs.freckle.packages.${system}; + freckleLib = inputs.freckle.lib.${system}; + in + rec { + packages = { + cabal = nixpkgs.cabal-install; + + fourmolu = freckle.fourmolu-0-13-x; + + ghc = freckleLib.haskellBundle { + ghcVersion = "ghc-9-6-3"; + enableHLS = true; + packageSelection = p: [ + p.aeson + p.aeson-casing + p.bytestring + p.hashable + p.http-conduit + p.iso8601-time + p.microlens + p.microlens-mtl + p.monad-logger + p.mtl + p.scientific + p.text + p.time + p.unliftio + p.unliftio-core + p.unordered-containers + ]; + }; + + hlint = + nixpkgs.haskell.lib.justStaticExecutables + nixpkgs.hlint; + + hiedb = + nixpkgs.haskell.lib.justStaticExecutables + nixpkgs.haskell.haskellPackages.hiedb; + + stack = nixpkgs.writeShellApplication { + name = "stack"; + text = '' + ${nixpkgs.stack}/bin/stack --system-ghc --no-nix "$@" + ''; + } + ; + }; + + devShells.default = nixpkgs.mkShell { + buildInputs = with (nixpkgs); [ + pcre + pcre.dev + zlib + zlib.dev + ]; + + nativeBuildInputs = with (packages); [ + cabal + fourmolu + ghc + hlint + stack + ]; + + shellHook = '' + export STACK_YAML=stack.yaml + ''; + }; + }); +} diff --git a/fourmolu.yaml b/fourmolu.yaml new file mode 100644 index 0000000..ef571e8 --- /dev/null +++ b/fourmolu.yaml @@ -0,0 +1,15 @@ +indentation: 2 +column-limit: 80 # ignored until v12 / ghc-9.6 +function-arrows: leading +comma-style: leading # default +import-export-style: leading +indent-wheres: false # default +record-brace-space: true +newlines-between-decls: 1 # default +haddock-style: single-line +let-style: mixed +in-style: left-align +single-constraint-parens: never # ignored until v12 / ghc-9.6 +unicode: never # default +respectful: true # default +fixities: [] # default diff --git a/library/Asana/Api/CustomField.hs b/library/Asana/Api/CustomField.hs index e6fed55..845cb97 100644 --- a/library/Asana/Api/CustomField.hs +++ b/library/Asana/Api/CustomField.hs @@ -1,15 +1,15 @@ module Asana.Api.CustomField - ( CustomField(..) - , CustomFields(..) - , customEnumId - , EnumOption(..) - , putCustomField - , putCustomFields - ) where - -import Asana.Api.Prelude + ( CustomField (..), + CustomFields (..), + customEnumId, + EnumOption (..), + putCustomField, + putCustomFields, + ) +where import Asana.Api.Gid (Gid, gidToText) +import Asana.Api.Prelude import Asana.Api.Request import Data.Aeson import Data.Aeson.Casing (aesonPrefix, snakeCase) @@ -22,27 +22,28 @@ data CustomField = CustomNumber Gid Text (Maybe Scientific) | CustomEnum Gid Text [EnumOption] (Maybe Text) | CustomText Gid Text (Maybe Text) - | Other -- ^ Unexpected types dumped here + | -- | Unexpected types dumped here + Other deriving stock (Eq, Generic, Show) -newtype CustomFields = CustomFields { getCustomFields :: [CustomField] } +newtype CustomFields = CustomFields {getCustomFields :: [CustomField]} deriving stock (Show, Eq) deriving newtype (FromJSON) instance ToJSON CustomFields where toJSON (CustomFields fields) = object $ concatMap toPair fields - where - toPair = \case - CustomNumber gid _ n -> [gidToKey gid .= n] - e@(CustomEnum gid _ _ _) -> [gidToKey gid .= customEnumId e] - _ -> [] + where + toPair = \case + CustomNumber gid _ n -> [gidToKey gid .= n] + e@(CustomEnum gid _ _ _) -> [gidToKey gid .= customEnumId e] + _ -> [] - -- fromString will give us Text for aeson-1.x and Key for aeson-2.x - gidToKey = fromString . T.unpack . gidToText + -- fromString will give us Text for aeson-1.x and Key for aeson-2.x + gidToKey = fromString . T.unpack . gidToText data EnumOption = EnumOption - { eoGid :: Gid - , eoName :: Text + { eoGid :: Gid, + eoName :: Text } deriving stock (Eq, Generic, Show) @@ -54,7 +55,6 @@ instance FromJSON EnumOption where -- - Must be a @'CustomEnum'@ -- - Must have a value -- - Must have an option with the same name as that value --- customEnumId :: CustomField -> Maybe Gid customEnumId (CustomEnum _ _ opts mValue) = do value <- mValue @@ -77,22 +77,24 @@ instance FromJSON CustomField where <*> (o .: "name") <*> (o .: "enum_options") <*> case value of - Object vo -> vo .:? "name" - _ -> pure Nothing + Object vo -> vo .:? "name" + _ -> pure Nothing _ -> pure Other -putCustomField - :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) - => Gid - -> CustomField - -> m () +putCustomField :: + (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) => + Gid -> + CustomField -> + m () putCustomField taskId = putCustomFields taskId . CustomFields . pure -putCustomFields - :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) - => Gid - -> CustomFields - -> m () +putCustomFields :: + (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) => + Gid -> + CustomFields -> + m () putCustomFields taskId fields = - void $ put ("/tasks/" <> T.unpack (gidToText taskId)) $ ApiData - (object ["custom_fields" .= fields]) + void $ + put ("/tasks/" <> T.unpack (gidToText taskId)) $ + ApiData + (object ["custom_fields" .= fields]) diff --git a/library/Asana/Api/Gid.hs b/library/Asana/Api/Gid.hs index 1252a96..0410dfb 100644 --- a/library/Asana/Api/Gid.hs +++ b/library/Asana/Api/Gid.hs @@ -1,24 +1,29 @@ -- | A globally unique identifier module Asana.Api.Gid - ( Gid - , AsanaReference(..) - , gidToText - , textToGid - ) where + ( Gid, + AsanaReference (..), + gidToText, + textToGid, + ) +where import Asana.Api.Prelude - import Data.Aeson - (FromJSON(..), FromJSONKey, ToJSON, ToJSONKey, genericParseJSON) + ( FromJSON (..), + FromJSONKey, + ToJSON, + ToJSONKey, + genericParseJSON, + ) import Data.Aeson.Casing (aesonPrefix, snakeCase) import Data.Hashable (Hashable) -newtype Gid = Gid { gidToText :: Text } +newtype Gid = Gid {gidToText :: Text} deriving stock (Eq, Generic, Show) deriving newtype (FromJSON, ToJSON, ToJSONKey, FromJSONKey, Hashable) -- | An object @{ gid: }@ -newtype AsanaReference = AsanaReference { arGid :: Gid } +newtype AsanaReference = AsanaReference {arGid :: Gid} deriving stock (Eq, Generic, Show) instance FromJSON AsanaReference where diff --git a/library/Asana/Api/Named.hs b/library/Asana/Api/Named.hs index 0284dc2..952443e 100644 --- a/library/Asana/Api/Named.hs +++ b/library/Asana/Api/Named.hs @@ -1,17 +1,17 @@ -- | Anything with a compact @{ id, name }@ representation module Asana.Api.Named - ( Named(..) - ) where - -import Asana.Api.Prelude + ( Named (..), + ) +where import Asana.Api.Gid (Gid) +import Asana.Api.Prelude import Data.Aeson (FromJSON, genericParseJSON, parseJSON) import Data.Aeson.Casing (aesonPrefix, snakeCase) data Named = Named - { nGid :: Gid - , nName :: Text + { nGid :: Gid, + nName :: Text } deriving stock (Eq, Generic, Show) diff --git a/library/Asana/Api/Prelude.hs b/library/Asana/Api/Prelude.hs index b529f89..96fea90 100644 --- a/library/Asana/Api/Prelude.hs +++ b/library/Asana/Api/Prelude.hs @@ -1,22 +1,36 @@ module Asana.Api.Prelude - ( module X - ) where - -import Prelude as X + ( module X, + ) +where import Control.Arrow as X ((&&&), (***)) +import Control.Monad as X (guard, when) import Control.Monad.IO.Unlift as X (MonadUnliftIO) import Control.Monad.Logger.CallStack as X import Control.Monad.Reader as X + ( MonadIO (..), + MonadReader (..), + MonadTrans (..), + Reader, + ReaderT (..), + ) import Data.Bifunctor as X (first, second) import Data.ByteString as X (ByteString) import Data.Foldable as X (for_) +import Data.Functor as X (void) import Data.Maybe as X - (catMaybes, fromMaybe, isJust, isNothing, listToMaybe, mapMaybe) + ( catMaybes, + fromMaybe, + isJust, + isNothing, + listToMaybe, + mapMaybe, + ) import Data.Text as X (Text, pack, unpack) import Data.Traversable as X (for) import GHC.Generics as X (Generic) import Lens.Micro as X (Lens', lens) import Lens.Micro.Mtl as X (view) import Text.Read as X (readMaybe) -import UnliftIO.Exception as X (Exception(..), catch, throwIO) +import UnliftIO.Exception as X (Exception (..), catch, throwIO) +import Prelude as X diff --git a/library/Asana/Api/Project.hs b/library/Asana/Api/Project.hs index 7cf14f4..3037e08 100644 --- a/library/Asana/Api/Project.hs +++ b/library/Asana/Api/Project.hs @@ -1,18 +1,18 @@ module Asana.Api.Project - ( Project(..) - ) where - -import Asana.Api.Prelude + ( Project (..), + ) +where import Asana.Api.Gid (Gid) +import Asana.Api.Prelude import Data.Aeson (FromJSON, genericParseJSON, parseJSON) import Data.Aeson.Casing (aesonPrefix, snakeCase) import Data.Time (UTCTime) data Project = Project - { pGid :: Gid - , pName :: Text - , pCreatedAt :: UTCTime + { pGid :: Gid, + pName :: Text, + pCreatedAt :: UTCTime } deriving stock (Generic, Show) diff --git a/library/Asana/Api/Request.hs b/library/Asana/Api/Request.hs index 5ea3382..c417901 100644 --- a/library/Asana/Api/Request.hs +++ b/library/Asana/Api/Request.hs @@ -1,20 +1,20 @@ module Asana.Api.Request - ( AsanaAccessKey(..) - , HasAsanaAccessKey(..) - , Single(..) - , Page(..) - , NextPage(..) - , ApiData(..) - , getAll - , getAllParams - , getSingle - , put - , post - , maxRequests - ) where + ( AsanaAccessKey (..), + HasAsanaAccessKey (..), + Single (..), + Page (..), + NextPage (..), + ApiData (..), + getAll, + getAllParams, + getSingle, + put, + post, + maxRequests, + ) +where import Asana.Api.Prelude - import Data.Aeson import Data.Aeson.Casing (aesonPrefix, snakeCase) import qualified Data.ByteString.Lazy as BSL @@ -22,23 +22,23 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Encoding.Error as T import Network.HTTP.Simple - ( JSONException(JSONConversionException, JSONParseException) - , Request - , Response - , addRequestHeader - , getResponseBody - , getResponseHeader - , getResponseStatusCode - , httpJSON - , parseRequest_ - , setRequestBodyJSON - , setRequestMethod + ( JSONException (JSONConversionException, JSONParseException), + Request, + Response, + addRequestHeader, + getResponseBody, + getResponseHeader, + getResponseStatusCode, + httpJSON, + parseRequest_, + setRequestBodyJSON, + setRequestMethod, ) import UnliftIO.Concurrent (threadDelay) newtype AsanaAccessKey = AsanaAccessKey - { unAsanaAccessKey :: Text - } + { unAsanaAccessKey :: Text + } class HasAsanaAccessKey env where asanaAccessKeyL :: Lens' env AsanaAccessKey @@ -50,30 +50,32 @@ maxRequests :: Int maxRequests = 50 -- | Type for a single-resource response, containing @{ data: { ... } }@ +type role Single representational newtype Single a = Single { sData :: a } deriving newtype (Eq, Show) - deriving stock Generic + deriving stock (Generic) -instance FromJSON a => FromJSON (Single a) where +instance (FromJSON a) => FromJSON (Single a) where parseJSON = genericParseJSON $ aesonPrefix snakeCase -- | Type for a list-resource response, containing @{ data: [{ ... }] }@ +type role Page representational data Page a = Page - { pData :: [a] - , pNextPage :: Maybe NextPage + { pData :: [a], + pNextPage :: Maybe NextPage } deriving stock (Eq, Generic, Show) -instance FromJSON a => FromJSON (Page a) where +instance (FromJSON a) => FromJSON (Page a) where parseJSON = genericParseJSON $ aesonPrefix snakeCase -- | The @next_page@ element of a paginated response data NextPage = NextPage - { npOffset :: Text - , npPath :: Text - , npUri :: Text + { npOffset :: Text, + npPath :: Text, + npUri :: Text } deriving stock (Eq, Generic, Show) @@ -81,141 +83,147 @@ instance FromJSON NextPage where parseJSON = genericParseJSON $ aesonPrefix snakeCase -- | Generic type for un/wrapping an item as @{ data: }@ +type role ApiData representational newtype ApiData a = ApiData { adData :: a } deriving newtype (Show, Eq) - deriving stock Generic + deriving stock (Generic) -instance FromJSON a => FromJSON (ApiData a) where +instance (FromJSON a) => FromJSON (ApiData a) where parseJSON = genericParseJSON $ aesonPrefix snakeCase -instance ToJSON a => ToJSON (ApiData a) where +instance (ToJSON a) => ToJSON (ApiData a) where toJSON = genericToJSON $ aesonPrefix snakeCase toEncoding = genericToEncoding $ aesonPrefix snakeCase -- | Naively GET all pages of a paginated resource -getAll - :: ( MonadUnliftIO m - , MonadLogger m - , MonadReader env m - , HasAsanaAccessKey env - , FromJSON a - ) - => String - -> m [a] +getAll :: + ( MonadUnliftIO m, + MonadLogger m, + MonadReader env m, + HasAsanaAccessKey env, + FromJSON a + ) => + String -> + m [a] getAll path = getAllParams path [] -getAllParams - :: ( MonadUnliftIO m - , MonadLogger m - , MonadReader env m - , HasAsanaAccessKey env - , FromJSON a - ) - => String - -> [(String, String)] - -> m [a] +getAllParams :: + ( MonadUnliftIO m, + MonadLogger m, + MonadReader env m, + HasAsanaAccessKey env, + FromJSON a + ) => + String -> + [(String, String)] -> + m [a] getAllParams path params = go Nothing - where - go mOffset = do - Page d mNextPage <- get path params 50 mOffset + where + go mOffset = do + Page d mNextPage <- get path params 50 mOffset - maybe (pure d) (fmap (d ++) . go . Just . T.unpack . npOffset) mNextPage + maybe (pure d) (fmap (d ++) . go . Just . T.unpack . npOffset) mNextPage -- | Get a single resource -getSingle - :: ( MonadUnliftIO m - , MonadLogger m - , MonadReader env m - , HasAsanaAccessKey env - , FromJSON a - ) - => String - -> m a +getSingle :: + ( MonadUnliftIO m, + MonadLogger m, + MonadReader env m, + HasAsanaAccessKey env, + FromJSON a + ) => + String -> + m a getSingle path = sData <$> get path [] 1 Nothing -get - :: ( MonadUnliftIO m - , MonadLogger m - , MonadReader env m - , HasAsanaAccessKey env - , FromJSON a - ) - => String - -> [(String, String)] - -> Int - -> Maybe String - -> m a +get :: + ( MonadUnliftIO m, + MonadLogger m, + MonadReader env m, + HasAsanaAccessKey env, + FromJSON a + ) => + String -> + [(String, String)] -> + Int -> + Maybe String -> + m a get path params limit mOffset = do AsanaAccessKey key <- view asanaAccessKeyL - let - request = - parseRequest_ - $ "https://app.asana.com/api/1.0" - <> path - <> "?limit=" - <> show limit -- Ignored on not paging responses - <> maybe "" ("&offset=" <>) mOffset - <> concatMap (\(k, v) -> "&" <> k <> "=" <> v) params + let request = + parseRequest_ $ + "https://app.asana.com/api/1.0" + <> path + <> "?limit=" + <> show limit -- Ignored on not paging responses + <> maybe "" ("&offset=" <>) mOffset + <> concatMap (\(k, v) -> "&" <> k <> "=" <> v) params response <- retry 50 $ httpJSON (addAuthorization key request) - when (300 <= getResponseStatusCode response) - $ logWarnNS "Asana" - $ "GET failed, status: " - <> pack (show $ getResponseStatusCode response) + when (300 <= getResponseStatusCode response) $ + logWarnNS "Asana" $ + "GET failed, status: " + <> pack (show $ getResponseStatusCode response) pure $ getResponseBody response -put - :: ( MonadUnliftIO m - , MonadLogger m - , MonadReader env m - , HasAsanaAccessKey env - , ToJSON a - ) - => String - -> a - -> m Value +put :: + ( MonadUnliftIO m, + MonadLogger m, + MonadReader env m, + HasAsanaAccessKey env, + ToJSON a + ) => + String -> + a -> + m Value put = httpAction "PUT" -post - :: ( MonadUnliftIO m - , MonadLogger m - , MonadReader env m - , HasAsanaAccessKey env - , ToJSON a - ) - => String - -> a - -> m Value +post :: + ( MonadUnliftIO m, + MonadLogger m, + MonadReader env m, + HasAsanaAccessKey env, + ToJSON a + ) => + String -> + a -> + m Value post = httpAction "POST" -httpAction - :: ( MonadUnliftIO m - , MonadLogger m - , MonadReader env m - , HasAsanaAccessKey env - , ToJSON a - ) - => ByteString - -> String - -> a - -> m Value +httpAction :: + ( MonadUnliftIO m, + MonadLogger m, + MonadReader env m, + HasAsanaAccessKey env, + ToJSON a + ) => + ByteString -> + String -> + a -> + m Value httpAction verb path payload = do AsanaAccessKey key <- view asanaAccessKeyL let request = parseRequest_ $ "https://app.asana.com/api/1.0" <> path - response <- retry 10 $ httpJSON - (setRequestMethod verb . setRequestBodyJSON payload $ addAuthorization - key - request - ) - when (300 <= getResponseStatusCode response) $ logWarnNS "Asana" $ mconcat - [ "Request failed" - , "\n method: " <> T.decodeUtf8 verb - , "\n status: " <> pack (show $ getResponseStatusCode response) - , "\n body : " <> T.decodeUtf8 - (BSL.toStrict $ encode $ toJSON $ getResponseBody @Value response) - ] + response <- + retry 10 $ + httpJSON + ( setRequestMethod verb . setRequestBodyJSON payload $ + addAuthorization + key + request + ) + when (300 <= getResponseStatusCode response) $ + logWarnNS "Asana" $ + mconcat + [ "Request failed", + "\n method: " <> T.decodeUtf8 verb, + "\n status: " <> pack (show $ getResponseStatusCode response), + "\n body : " + <> T.decodeUtf8 + (BSL.toStrict $ encode $ toJSON $ getResponseBody @Value response) + ] pure $ getResponseBody response @@ -223,38 +231,38 @@ addAuthorization :: Text -> Request -> Request addAuthorization key = addRequestHeader "Authorization" $ "Bearer " <> T.encodeUtf8 key -retry - :: forall a m - . (MonadUnliftIO m, MonadLogger m) - => Int - -> m (Response a) - -> m (Response a) +retry :: + forall a m. + (MonadUnliftIO m, MonadLogger m) => + Int -> + m (Response a) -> + m (Response a) retry attempt go | attempt <= 0 = go | otherwise = handler =<< go `catch` handleParseError - where - handleParseError :: JSONException -> m (Response a) - handleParseError e = case e of - JSONParseException _ rsp _ -> orThrow e rsp - JSONConversionException _ rsp _ -> orThrow e rsp + where + handleParseError :: JSONException -> m (Response a) + handleParseError e = case e of + JSONParseException _ rsp _ -> orThrow e rsp + JSONConversionException _ rsp _ -> orThrow e rsp - orThrow :: Exception e => e -> Response b -> m (Response a) - orThrow e response - | getResponseStatusCode response == 429 = do - let seconds = getResponseDelay response - logWarnNS "Asana" $ "Retrying after " <> pack (show seconds) <> " seconds" - threadDelay $ seconds * 1000000 - retry (pred attempt) go - | otherwise = liftIO $ throwIO e + orThrow :: (Exception e) => e -> Response b -> m (Response a) + orThrow e response + | getResponseStatusCode response == 429 = do + let seconds = getResponseDelay response + logWarnNS "Asana" $ "Retrying after " <> pack (show seconds) <> " seconds" + threadDelay $ seconds * 1000000 + retry (pred attempt) go + | otherwise = liftIO $ throwIO e - handler :: Response a -> m (Response a) - handler response - | getResponseStatusCode response == 429 = do - let seconds = getResponseDelay response - logWarnNS "Asana" $ "Retrying after " <> pack (show seconds) <> " seconds" - threadDelay $ seconds * 100000 - retry (pred attempt) go - | otherwise = pure response + handler :: Response a -> m (Response a) + handler response + | getResponseStatusCode response == 429 = do + let seconds = getResponseDelay response + logWarnNS "Asana" $ "Retrying after " <> pack (show seconds) <> " seconds" + threadDelay $ seconds * 100000 + retry (pred attempt) go + | otherwise = pure response getResponseDelay :: Response a -> Int getResponseDelay = diff --git a/library/Asana/Api/Tag.hs b/library/Asana/Api/Tag.hs index 043c41f..1187087 100644 --- a/library/Asana/Api/Tag.hs +++ b/library/Asana/Api/Tag.hs @@ -1,16 +1,16 @@ module Asana.Api.Tag - ( Tag(..) - ) where - -import Asana.Api.Prelude + ( Tag (..), + ) +where import Asana.Api.Gid +import Asana.Api.Prelude import Data.Aeson import Data.Aeson.Casing (aesonPrefix, snakeCase) data Tag = Tag - { tGid :: Gid - , tName :: Text + { tGid :: Gid, + tName :: Text } deriving stock (Eq, Generic, Show) diff --git a/library/Asana/Api/Task.hs b/library/Asana/Api/Task.hs index b9e368b..10ed634 100644 --- a/library/Asana/Api/Task.hs +++ b/library/Asana/Api/Task.hs @@ -1,25 +1,25 @@ module Asana.Api.Task - ( Task(..) - , Membership(..) - , TaskStatusFilter(..) - , ResourceSubtype(..) - , PostTask(..) - , getTask - , getProjectTasks - , getProjectTasksCompletedSince - , postTask - , addTag - , putCompleted - , taskUrl - , extractNumberField - , extractEnumField - ) where - -import Asana.Api.Prelude + ( Task (..), + Membership (..), + TaskStatusFilter (..), + ResourceSubtype (..), + PostTask (..), + getTask, + getProjectTasks, + getProjectTasksCompletedSince, + postTask, + addTag, + putCompleted, + taskUrl, + extractNumberField, + extractEnumField, + ) +where import Asana.Api.CustomField import Asana.Api.Gid import Asana.Api.Named +import Asana.Api.Prelude import Asana.Api.Request import Asana.Api.Tag import Data.Aeson @@ -30,8 +30,8 @@ import Data.Time (UTCTime, getCurrentTime) import Data.Time.ISO8601 (formatISO8601) data Membership = Membership - { mProject :: Named - , mSection :: Maybe Named + { mProject :: Named, + mSection :: Maybe Named } deriving stock (Eq, Generic, Show) @@ -43,21 +43,21 @@ data ResourceSubtype = DefaultTask | Milestone | Section instance FromJSON ResourceSubtype where parseJSON = - genericParseJSON $ defaultOptions { constructorTagModifier = snakeCase } + genericParseJSON $ defaultOptions {constructorTagModifier = snakeCase} data Task = Task - { tAssignee :: Maybe Named - , tName :: Text - , tCompleted :: Bool - , tCompletedAt :: Maybe UTCTime - , tCreatedAt :: UTCTime - , tCustomFields :: CustomFields - , tMemberships :: [Membership] - , tGid :: Gid - , tResourceSubtype :: ResourceSubtype - , tNotes :: Text - , tProjects :: [AsanaReference] - , tTags :: [Tag] + { tAssignee :: Maybe Named, + tName :: Text, + tCompleted :: Bool, + tCompletedAt :: Maybe UTCTime, + tCreatedAt :: UTCTime, + tCustomFields :: CustomFields, + tMemberships :: [Membership], + tGid :: Gid, + tResourceSubtype :: ResourceSubtype, + tNotes :: Text, + tProjects :: [AsanaReference], + tTags :: [Tag] } deriving stock (Eq, Generic, Show) @@ -65,20 +65,20 @@ instance FromJSON Task where parseJSON = genericParseJSON $ aesonPrefix snakeCase -- | Return all details for a task by id -getTask - :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) - => Gid - -> m Task +getTask :: + (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) => + Gid -> + m Task getTask taskId = getSingle $ "/tasks/" <> T.unpack (gidToText taskId) data PostTask = PostTask - { ptProjects :: [Gid] - , ptCustomFields :: HashMap Gid Text - , ptName :: Text - , ptNotes :: Text - , ptParent :: Maybe Gid + { ptProjects :: [Gid], + ptCustomFields :: HashMap Gid Text, + ptName :: Text, + ptNotes :: Text, + ptParent :: Maybe Gid } - deriving stock Generic + deriving stock (Generic) instance FromJSON PostTask where parseJSON = genericParseJSON $ aesonPrefix snakeCase @@ -88,10 +88,10 @@ instance ToJSON PostTask where toEncoding = genericToEncoding $ aesonPrefix snakeCase -- | Create a new 'Task' -postTask - :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) - => PostTask - -> m (Result Task) +postTask :: + (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) => + PostTask -> + m (Result Task) postTask body = fmap adData . fromJSON <$> post "/tasks" (ApiData body) -- | Return compact task details for a project @@ -99,51 +99,55 @@ postTask body = fmap adData . fromJSON <$> post "/tasks" (ApiData body) -- Iterating ourselves and returning @['Task']@ is a better interface but -- precludes us logging things each time we request an element. So we return -- @'Named'@ for now and let the caller use @'getTask'@ themselves. --- -getProjectTasks - :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) - => Gid - -> TaskStatusFilter - -> m [Named] +getProjectTasks :: + (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) => + Gid -> + TaskStatusFilter -> + m [Named] getProjectTasks projectId taskStatusFilter = do now <- liftIO getCurrentTime getAllParams (T.unpack $ "/projects/" <> gidToText projectId <> "/tasks") (completedSince now) - - where - completedSince now = case taskStatusFilter of - AllTasks -> [] - IncompletedTasks -> [("completed_since", formatISO8601 now)] + where + completedSince now = case taskStatusFilter of + AllTasks -> [] + IncompletedTasks -> [("completed_since", formatISO8601 now)] data TaskStatusFilter = IncompletedTasks | AllTasks -getProjectTasksCompletedSince - :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) - => Gid - -> UTCTime - -> m [Named] -getProjectTasksCompletedSince projectId since = getAllParams - (T.unpack $ "/projects/" <> gidToText projectId <> "/tasks") - [("completed_since", formatISO8601 since)] - -addTag - :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) - => Gid - -> Gid -- ^ Tag - -> m () +getProjectTasksCompletedSince :: + (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) => + Gid -> + UTCTime -> + m [Named] +getProjectTasksCompletedSince projectId since = + getAllParams + (T.unpack $ "/projects/" <> gidToText projectId <> "/tasks") + [("completed_since", formatISO8601 since)] + +addTag :: + (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) => + Gid -> + -- | Tag + Gid -> + m () addTag task tag = - void $ post ("/tasks/" <> T.unpack (gidToText task) <> "/addTag") $ ApiData - (object ["tag" .= tag]) - -putCompleted - :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) - => Gid - -> Bool - -> m () + void $ + post ("/tasks/" <> T.unpack (gidToText task) <> "/addTag") $ + ApiData + (object ["tag" .= tag]) + +putCompleted :: + (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) => + Gid -> + Bool -> + m () putCompleted taskId completed = - void $ put ("/tasks/" <> T.unpack (gidToText taskId)) $ ApiData - (object ["completed" .= completed]) + void $ + put ("/tasks/" <> T.unpack (gidToText taskId)) $ + ApiData + (object ["completed" .= completed]) taskUrl :: Task -> Text taskUrl Task {..} = "https://app.asana.com/0/0/" <> gidToText tGid <> "/f" diff --git a/library/Asana/Api/Task/Search.hs b/library/Asana/Api/Task/Search.hs index bde3415..8dad198 100644 --- a/library/Asana/Api/Task/Search.hs +++ b/library/Asana/Api/Task/Search.hs @@ -1,13 +1,13 @@ module Asana.Api.Task.Search - ( SearchWorkspace(..) - , TaskTypeFilter(..) - , searchWorkspace - ) where - -import Asana.Api.Prelude + ( SearchWorkspace (..), + TaskTypeFilter (..), + searchWorkspace, + ) +where import Asana.Api.Gid import Asana.Api.Named +import Asana.Api.Prelude import Asana.Api.Request import Asana.Api.Task import Data.HashMap.Strict (HashMap) @@ -18,40 +18,40 @@ import qualified Data.Text as T data TaskTypeFilter = TasksOnly | SubtasksOnly | AllTaskTypes data SearchWorkspace = SearchWorkspace - { swWorkspaceId :: Gid - , swProjectIds :: [Gid] - , swTaskStatusFilter :: TaskStatusFilter - , swCustomFields :: HashMap Gid Text - , swTaskTypeFilter :: TaskTypeFilter + { swWorkspaceId :: Gid, + swProjectIds :: [Gid], + swTaskStatusFilter :: TaskStatusFilter, + swCustomFields :: HashMap Gid Text, + swTaskTypeFilter :: TaskTypeFilter } -- | Search for tasks within a workspace -searchWorkspace - :: (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) - => SearchWorkspace - -> m [Named] +searchWorkspace :: + (MonadUnliftIO m, MonadLogger m, MonadReader env m, HasAsanaAccessKey env) => + SearchWorkspace -> + m [Named] searchWorkspace SearchWorkspace {..} = getAllParams - (T.unpack $ "/workspaces/" <> gidToText swWorkspaceId <> "/tasks/search") - $ ( "projects.all" - , intercalate "," $ map (T.unpack . gidToText) swProjectIds + (T.unpack $ "/workspaces/" <> gidToText swWorkspaceId <> "/tasks/search") + $ ( "projects.all", + intercalate "," $ map (T.unpack . gidToText) swProjectIds ) - : customFieldParams - <> completed - <> isSubtask - where - customFieldParams = - map - (\(a, b) -> - ("custom_fields." <> T.unpack (gidToText a) <> ".value", T.unpack b) + : customFieldParams + <> completed + <> isSubtask + where + customFieldParams = + map + ( \(a, b) -> + ("custom_fields." <> T.unpack (gidToText a) <> ".value", T.unpack b) ) - $ HashMap.toList swCustomFields + $ HashMap.toList swCustomFields - completed = case swTaskStatusFilter of - AllTasks -> [] - IncompletedTasks -> [("completed", "false")] + completed = case swTaskStatusFilter of + AllTasks -> [] + IncompletedTasks -> [("completed", "false")] - isSubtask = case swTaskTypeFilter of - AllTaskTypes -> [] - TasksOnly -> [("is_subtask", "false")] - SubtasksOnly -> [("is_subtask", "true")] + isSubtask = case swTaskTypeFilter of + AllTaskTypes -> [] + TasksOnly -> [("is_subtask", "false")] + SubtasksOnly -> [("is_subtask", "true")] diff --git a/package.yaml b/package.yaml index 5394bc9..73d7c2f 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: asana -version: 1.0.1.0 +version: 1.0.1.1 maintainer: Freckle Education category: Utils github: freckle/asana-hs @@ -55,6 +55,7 @@ default-extensions: - OverloadedStrings - RankNTypes - RecordWildCards + - RoleAnnotations - ScopedTypeVariables - StandaloneDeriving - TypeApplications diff --git a/stack-lts-19.6.yaml b/stack-lts-19.6.yaml new file mode 100644 index 0000000..82823e4 --- /dev/null +++ b/stack-lts-19.6.yaml @@ -0,0 +1 @@ +resolver: lts-19.6 diff --git a/stack-lts-19.6.yaml.lock b/stack-lts-19.6.yaml.lock new file mode 100644 index 0000000..d8ab114 --- /dev/null +++ b/stack-lts-19.6.yaml.lock @@ -0,0 +1,12 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: [] +snapshots: +- completed: + size: 618876 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/6.yaml + sha256: fb634b19f31da06684bb07ce02a20c75a3162138f279b388905b03ebd57bb50f + original: lts-19.6 diff --git a/stack-lts-20.26.yaml b/stack-lts-20.26.yaml new file mode 100644 index 0000000..fc9172f --- /dev/null +++ b/stack-lts-20.26.yaml @@ -0,0 +1 @@ +resolver: lts-20.26 diff --git a/stack-lts-20.26.yaml.lock b/stack-lts-20.26.yaml.lock new file mode 100644 index 0000000..ea5a850 --- /dev/null +++ b/stack-lts-20.26.yaml.lock @@ -0,0 +1,12 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: [] +snapshots: +- completed: + sha256: 5a59b2a405b3aba3c00188453be172b85893cab8ebc352b1ef58b0eae5d248a2 + size: 650475 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/26.yaml + original: lts-20.26 diff --git a/stack-lts-21.25.yaml b/stack-lts-21.25.yaml new file mode 100644 index 0000000..377040a --- /dev/null +++ b/stack-lts-21.25.yaml @@ -0,0 +1 @@ +resolver: lts-21.25 diff --git a/stack-lts-21.25.yaml.lock b/stack-lts-21.25.yaml.lock new file mode 100644 index 0000000..f823d29 --- /dev/null +++ b/stack-lts-21.25.yaml.lock @@ -0,0 +1,12 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: [] +snapshots: +- completed: + sha256: a81fb3877c4f9031e1325eb3935122e608d80715dc16b586eb11ddbff8671ecd + size: 640086 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/25.yaml + original: lts-21.25 diff --git a/stack-nightly.yaml b/stack-nightly.yaml index 992c48c..590f288 100644 --- a/stack-nightly.yaml +++ b/stack-nightly.yaml @@ -1 +1 @@ -resolver: nightly-2022-05-20 +resolver: nightly-2024-01-16 diff --git a/stack-nightly.yaml.lock b/stack-nightly.yaml.lock index eedded3..a53deab 100644 --- a/stack-nightly.yaml.lock +++ b/stack-nightly.yaml.lock @@ -6,7 +6,7 @@ packages: [] snapshots: - completed: - size: 588043 - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/5/20.yaml - sha256: 7800e52de866bab899c118558f3a48e455f9f57fb3b3595e0002018fbea5ee58 - original: nightly-2022-05-20 + sha256: 7b31bbeee436dd84cc9b06c736be3ebfddc18dfc1193a8e19b6ddcc84bfb4b3e + size: 556888 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2024/1/16.yaml + original: nightly-2024-01-16 diff --git a/stack.yaml b/stack.yaml index 82823e4..ae77d02 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1 +1 @@ -resolver: lts-19.6 +resolver: lts-22.6 diff --git a/stack.yaml.lock b/stack.yaml.lock index d8ab114..1b74b0a 100644 --- a/stack.yaml.lock +++ b/stack.yaml.lock @@ -6,7 +6,7 @@ packages: [] snapshots: - completed: - size: 618876 - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/6.yaml - sha256: fb634b19f31da06684bb07ce02a20c75a3162138f279b388905b03ebd57bb50f - original: lts-19.6 + sha256: 1b4c2669e26fa828451830ed4725e4d406acc25a1fa24fcc039465dd13d7a575 + size: 714100 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/6.yaml + original: lts-22.6