diff --git a/Changelog.md b/Changelog.md
index 6a24e60ca4..0cf2349b89 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -3,6 +3,7 @@
## v3.9.14
- Update cargo strategy to parse new `cargo metadata` format for cargo >= 1.77.0 ([#1416](https://github.com/fossas/fossa-cli/pull/1416)).
- `fossa release-group`: Add command to create a FOSSA release group release (`fossa release-group create-release`) [#1409](https://github.com/fossas/fossa-cli/pull/1409).
+- `fossa project`: Adds commands to interact with FOSSA projects (`fossa project edit`) [#1394](https://github.com/fossas/fossa-cli/pull/1395).
## v3.9.13
- Support GIT dependencies in Bundler projects ([#1403](https://github.com/fossas/fossa-cli/pull/1403/files))
diff --git a/docs/README.md b/docs/README.md
index e782d3aa63..6918a26336 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -134,6 +134,7 @@ Concept guides explain the nuances behind how basic FOSSA primitives work. If yo
- [`fossa snippets`](./references/subcommands/snippets.md): Analyze snippets of a project and check if they exist in other open source projects FOSSA knows about.
- [`fossa test`](./references/subcommands/test.md): View the results of the most recent scan of a project.
- [`fossa release-group`](./references/subcommands/release-group.md): Interact with FOSSA release groups.
+- [`fossa project`](./references/subcommands/project.md): Interact with FOSSA projects.
#### Configuration
diff --git a/docs/references/files/fossa-yml.md b/docs/references/files/fossa-yml.md
index 94165e2095..2161d64af5 100644
--- a/docs/references/files/fossa-yml.md
+++ b/docs/references/files/fossa-yml.md
@@ -12,9 +12,13 @@ server: https://app.fossa.com
apiKey: a1b2c3
project:
+ locator: custom+1/github.com/fossas/fossa-cli
id: github.com/fossas/fossa-cli
name: fossa-cli
team: cli-team
+ teams:
+ - cli-team-1
+ - cli-team-2
policy: custom-cli-policy
link: fossa.com
url: github.com/fossas/fossa-cli
@@ -123,6 +127,11 @@ The project fields allow you to configure settings for the project you are inter
> Note: `name`, `team`, `policy`, `link`, and `jiraProjectKey` can only be set when creating a project (running `fossa analyze` for the first time). Otherwise, they will be silently ignored (we would like to make this a visible warning in the future).
+#### `project.locator:`
+The project Locator defines a unique ID that the FOSSA API will use to reference this project within FOSSA. The project locator can be found in the UI on the project `Settings` page listed as the `Project Locator` underneath the `Project Title` setting.
+
+
+
#### `project.id:`
The project ID defines an ID that is used to reference a project within your FOSSA organization. The project ID is a specific portion of the project locator and can be found in the UI on the project `Settings` page listed as the "Project Locator" underneath the "Project Title" setting. For example, if the "Project Locator" value of `custom+1/foo` is provided in the FOSSA UI, use `foo` for the `project.id`.
@@ -144,6 +153,12 @@ The name field sets the projects visible name in the FOSSA dashboard. By default
#### `project.team:`
The name of the team in your FOSSA organization to associate this project with.
+#### `project.teams:`
+The name of the teams in your FOSSA organization to associate this project with.
+
+>NOTE:
+ Currently, ONLY `fossa project edit` utilizes this field. Use [fossa project edit](../subcommands/project/edit.md) to add a project to all teams in the list.
+
#### `project.policy:`
The name of the policy in your FOSSA organization to associate this project with.
Mutually excludes `project.policyId`.
diff --git a/docs/references/files/fossa-yml.v3.schema.json b/docs/references/files/fossa-yml.v3.schema.json
index 1f28f453ff..47c209563b 100644
--- a/docs/references/files/fossa-yml.v3.schema.json
+++ b/docs/references/files/fossa-yml.v3.schema.json
@@ -7,10 +7,15 @@
"type": "object",
"description": "The project fields allow you to configure settings for the project you are interacting with through the FOSSA API.",
"properties": {
+ "locator": {
+ "type": "string",
+ "minLength": 1,
+ "description": "The project Locator defines a unique ID that the FOSSA API will use to reference this project within FOSSA. The project locator can be found in the UI on the project `Settings` page listed as the `Project Locator` underneath the `Project Title` setting."
+ },
"id": {
"type": "string",
"minLength": 1,
- "description": "The project ID defines a unique ID that the FOSSA API will use to reference this project. The project ID can be found in the UI on the project settings page listed as the `Project Locator` underneath the `Project Title` setting.\n\nBy default, it will use git remote origin url as project id if it's git repository. If it does not recognize version control system (vcs), project directory's name will be used."
+ "description": "The project ID defines a unique ID that the FOSSA API will use to reference this project within your organization. The project ID is a specific portion of the project locator and can be found in the UI on the project `Settings` page listed as the `Project Locator` underneath the `Project Title` setting.\n\nBy default, it will use git remote origin url as project id if it's git repository. If it does not recognize version control system (vcs), project directory's name will be used."
},
"name": {
"type": "string",
@@ -22,6 +27,13 @@
"minLength": 1,
"description": "The name of the team in your FOSSA organization to associate this project with."
},
+ "teams": {
+ "type": "array",
+ "description": "A list of team names in your FOSSA organization to associate this project with.",
+ "items": {
+ "type": "string"
+ }
+ },
"policy": {
"type": "string",
"minLength": 1,
diff --git a/docs/references/subcommands/project.md b/docs/references/subcommands/project.md
new file mode 100644
index 0000000000..56fb21ae96
--- /dev/null
+++ b/docs/references/subcommands/project.md
@@ -0,0 +1,27 @@
+## `fossa project`
+
+This `fossa project` subcommand allows users to interact with FOSSA projects.
+
+It has the following subcommand:
+
+- [`fossa project edit`](./project/edit.md)
+
+See the pages linked above for more details.
+
+### `fossa project edit`
+
+Edits a FOSSA project's settings and configurations.
+
+Example:
+
+```bash
+fossa project edit --project-locator custom+1/example --title example-title --project-url github.com/fossas/fossa-cli --jira-project-key example-jira-key --link fossa.com --team example-team-1 --team example-team-2 --policy example-policy --project-label example-label-1 --project-label example-label-2
+```
+
+### F.A.Q.
+
+1. Where can I find my project locator?
+
+The project Locator defines a unique ID that the FOSSA API will use to reference this project within FOSSA. The project locator can be found in the UI on the project `Settings` page listed as the "Project Locator" underneath the "Project Title" setting.
+
+
diff --git a/docs/references/subcommands/project/edit.md b/docs/references/subcommands/project/edit.md
new file mode 100644
index 0000000000..8ea3d727b9
--- /dev/null
+++ b/docs/references/subcommands/project/edit.md
@@ -0,0 +1,113 @@
+## `fossa project edit`
+
+`fossa project edit` allows you to edit a FOSSA project's settings and configuration.
+
+## Options
+
+Argument | Required | Description
+-----------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------
+`--config` / `-c` | No | The to your path to your `.fossa.yml`.
+`--project-locator` | Yes | The project Locator defines a unique ID that the FOSSA API will use to reference this project within FOSSA. The project locator can be found in the UI on the project `Settings` page listed as the "Project Locator" underneath the "Project Title" setting.
+`--project-id` | Yes | The project ID defines an ID that is used to reference a project within your FOSSA organization. The project ID is a specific portion of the project locator and can be found in the UI on the project `Settings` page listed as the "Project Locator" underneath the "Project Title" setting. For example, if the "Project Locator" value of `custom+1/foo` is provided in the FOSSA UI, use `foo`. Project ID defaults to the .git/config file or project's remote "origin" URL (Git), "Repository Root" obtained using 'svn info' (SVN), or the name of the project's directory (No VCS), if project ID wasn't explicityly set during project creation.
+`--title` / `-t` | No | The title of the FOSSA project.
+`--project-url` | No | The url of the project's repository.
+`--jira-project-key` / `-j` | No | The JIRA project key to associate to the FOSSA project.
+`--link` / `-L` | No | A link to attach to the FOSSA project.
+`--team` / `T` | No | The name of the team that will be associated with the FOSSA project. Specify multiple options by providing this argument multiple times.
+`--project-label` | No | The labels associated with the FOSSA project. Assign up to 5 labels for a project. Specify multiple options by providing this argument multiple times.
+
+> NOTE: The arguments listed as `Required` need to be provided through CLI options OR through your `.fossa.yml` configuration.
+
+> NOTE: Either project ID OR project locator needs to be set. Project ID takes precedence over project locator. For more details on the differences between project ID and project locator refer to [documentation](../../files/fossa-yml.md#what-is-the-difference-between-project-id-and-project-locator).
+
+## .fossa.yml Configuration
+
+All of the previously mentioned CLI options can be provided through a `.fossa.yml`. Refer to [fossa configuration](../../files/fossa-yml.md) to set up your `.fossa.yml`.
+
+> NOTE: CLI options take precedence over the configurations in `.fossa.yml`.
+
+## `fossa project edit` usage and guidance
+
+### Updating a project's labels
+
+When updating project labels through `fossa project edit`, the transaction is all or nothing. This means that the project labels specified through this command will overwrite the existing labels that are associated with the project. Be sure to include all the labels that you want to be associated with the project, even if some labels are already currently set.
+
+### Updating the teams associated with a project
+
+Currently, `fossa project edit` only supports adding a project to the teams that are specified through the command. There will be support to remove a project from the provided teams in the future.
+
+Providing teams for `fossa project edit` takes the following precdence:
+
+1. CLI options - Adds the project to teams (1 or many)
+2. `project.teams` in `.fossa.yml` - Adds the project to teams (1 or many)
+3. `project.team` in `.fossa.yml` - Adds the project to a team
+
+There is support for `project.team` due to backwards compatability as `project.teams` is a newly added field in `.fossa.yml`.
+
+## Example
+
+### Project locator example
+Given a project with project locator: `custom+1/example`, the following command:
+
+- Sets the project's title to `example-title`
+- Set the the project's url to `github.com/fossas/fossa-cli`
+- Sets the project's JIRA key to `example-jira-key`
+- Attaches link: `fossa.com` to the project
+- Adds the project to teams: `example-team-1`, `example-team-2`
+- Attaches policy: `example-policy` to the project
+- Attaches labels: `example-label-1` , `example-label-2` to the project
+
+```bash
+fossa project edit --project-locator custom+1/example --title example-title --project-url github.com/fossas/fossa-cli --jira-project-key example-jira-key --link fossa.com --team example-team --team example-team-2 --policy example-policy --project-label example-label-1 --project-label example-label-2
+```
+
+Similarly, you can you achieve the same result by running the following command with the given `.fossa.yml` configuration:
+
+```bash
+fossa project edit --config /path/to/config
+```
+
+```yaml
+project:
+ locator: custom+1/example
+ name: example-title
+ teams:
+ - example-team-1
+ - example-team-2
+ policy: example-policy
+ link: fossa.com
+ url: github.com/fossas/fossa-cli
+ jiraProjectKey: example-jira-key
+ labels:
+ - example-label-1
+ - example-label-2
+```
+
+### Project ID example
+Achieve the same result as defined above (for projects created through the CLI) using project ID:
+
+```bash
+fossa project edit --project-id example --title example-title --project-url github.com/fossas/fossa-cli --jira-project-key example-jira-key --link fossa.com --team example-team --team example-team-2 --policy example-policy --project-label example-label-1 --project-label example-label-2
+```
+
+Similarly, you can you achieve the same result by running the following command with the given `.fossa.yml` configuration:
+
+```bash
+fossa project edit --config /path/to/config
+```
+
+```yaml
+project:
+ id: example
+ name: example-title
+ teams:
+ - example-team-1
+ - example-team-2
+ policy: example-policy
+ link: fossa.com
+ url: github.com/fossas/fossa-cli
+ jiraProjectKey: example-jira-key
+ labels:
+ - example-label-1
+ - example-label-2
+```
diff --git a/spectrometer.cabal b/spectrometer.cabal
index 74a973e1db..8e887bdc1b 100644
--- a/spectrometer.cabal
+++ b/spectrometer.cabal
@@ -189,6 +189,7 @@ library
App.Fossa.Analyze.Upload
App.Fossa.API.BuildLink
App.Fossa.API.BuildWait
+ App.Fossa.ApiUtils
App.Fossa.ArchiveUploader
App.Fossa.BinaryDeps
App.Fossa.BinaryDeps.Jar
@@ -205,6 +206,8 @@ library
App.Fossa.Config.LicenseScan
App.Fossa.Config.LinkUserBinaries
App.Fossa.Config.ListTargets
+ App.Fossa.Config.Project
+ App.Fossa.Config.Project.Edit
App.Fossa.Config.ReleaseGroup
App.Fossa.Config.ReleaseGroup.AddProjects
App.Fossa.Config.ReleaseGroup.Common
@@ -238,6 +241,8 @@ library
App.Fossa.ManualDeps
App.Fossa.PathDependency
App.Fossa.PreflightChecks
+ App.Fossa.Project
+ App.Fossa.Project.Edit
App.Fossa.ProjectInference
App.Fossa.Reachability.Gradle
App.Fossa.Reachability.Jar
@@ -379,6 +384,7 @@ library
Effect.Grapher
Effect.Logger
Effect.ReadFS
+ Fossa.API.CoreTypes
Fossa.API.Types
Graphing
Graphing.Debug
@@ -541,6 +547,7 @@ test-suite unit-tests
App.Fossa.AnalyzeSpec
App.Fossa.API.BuildLinkSpec
App.Fossa.API.BuildWaitSpec
+ App.Fossa.ApiUtilsSpec
App.Fossa.ArchiveUploaderSpec
App.Fossa.BinaryDeps.JarSpec
App.Fossa.Config.AnalyzeSpec
@@ -558,6 +565,7 @@ test-suite unit-tests
App.Fossa.ManualDepsSpec
App.Fossa.PathDependencySpec
App.Fossa.PreflightChecksSpec
+ App.Fossa.Project.EditSpec
App.Fossa.ProjectInferenceSpec
App.Fossa.ReleaseGroup.AddProjectsSpec
App.Fossa.ReleaseGroup.CommonSpec
diff --git a/src/App/Fossa/ApiUtils.hs b/src/App/Fossa/ApiUtils.hs
new file mode 100644
index 0000000000..7a1ba978a3
--- /dev/null
+++ b/src/App/Fossa/ApiUtils.hs
@@ -0,0 +1,66 @@
+module App.Fossa.ApiUtils (
+ retrievePolicyId,
+ retrieveTeamIds,
+ retrieveLabelIds,
+ retrieveTeamIdsWithMaybe,
+) where
+
+import Control.Algebra (Has)
+import Control.Effect.Diagnostics (Diagnostics, errHelp, fatalText)
+import Data.Map qualified as Map
+import Data.Maybe (mapMaybe)
+import Data.Text (Text, intercalate)
+import Effect.Logger (pretty, renderIt, vsep)
+import Fossa.API.CoreTypes (Label (..), Labels (..), Policy (..), PolicyType (..), Team (..))
+
+retrievePolicyId :: Has Diagnostics sig m => Maybe Text -> PolicyType -> [Policy] -> m (Maybe Int)
+retrievePolicyId maybeTitle targetType policies = case maybeTitle of
+ Nothing -> pure Nothing
+ Just targetTitle -> do
+ let filteredPolicies = filter (\p -> policyTitle p == targetTitle && (policyType p == targetType)) policies
+ case filteredPolicies of
+ [] -> fatalText $ "Policy `" <> targetTitle <> "` not found"
+ [policy] -> pure . Just $ policyId policy
+ (_ : _ : _) ->
+ errHelp ("Navigate to the FOSSA web UI to rename your policies so that they are unqiue" :: Text)
+ . fatalText
+ $ "Multiple policies with title `" <> targetTitle <> "` found. Unable to determine which policy to use."
+
+retrieveTeamIdsWithMaybe :: Has Diagnostics sig m => Maybe [Text] -> [Team] -> m (Maybe [Int])
+retrieveTeamIdsWithMaybe maybeTeamNames teams = case maybeTeamNames of
+ Nothing -> pure Nothing
+ Just teamNames -> Just <$> retrieveTeamIds teamNames teams
+
+retrieveTeamIds :: Has Diagnostics sig m => [Text] -> [Team] -> m [Int]
+retrieveTeamIds teamNames teams = do
+ let teamMap = Map.fromList $ map (\team -> (teamName team, teamId team)) teams
+ validTeamIds = mapMaybe (`Map.lookup` teamMap) teamNames
+
+ if length teamNames == length validTeamIds
+ then pure validTeamIds
+ else do
+ let missingTeamNames = filter (`Map.notMember` teamMap) teamNames
+ fatalText $ "Teams " <> intercalate "," missingTeamNames <> " not found"
+
+retrieveLabelIds :: Has Diagnostics sig m => [Text] -> Labels -> m ([Int], Maybe [Text])
+retrieveLabelIds projectLabels (Labels orgLabels) = do
+ let orgLabelMap = Map.fromList $ map (\label -> (labelName label, labelId label)) orgLabels
+ go orgLabelMap projectLabels []
+ where
+ go :: Has Diagnostics sig m => Map.Map Text Int -> [Text] -> [Int] -> m ([Int], Maybe [Text])
+ go _ [] acc = pure (acc, Nothing)
+ go labelMap (x : xs) acc = do
+ case Map.lookup x labelMap of
+ Just labelId' -> go labelMap xs (labelId' : acc)
+ Nothing -> do
+ (labelIds, maybeWarnings) <- go labelMap xs acc
+ let warning =
+ renderIt $
+ vsep
+ [ "Label `" <> pretty x <> "` does not exist"
+ , "Navigate to `Organization Settings` in the FOSSA web UI to create new labels: https://app.fossa.com/account/settings/organization"
+ ]
+ let updatedWarnings = case maybeWarnings of
+ Just warnings -> Just (warning : warnings)
+ Nothing -> Just [warning]
+ pure (labelIds, updatedWarnings)
diff --git a/src/App/Fossa/Config/Common.hs b/src/App/Fossa/Config/Common.hs
index d58ce0cbbe..159b59c94b 100644
--- a/src/App/Fossa/Config/Common.hs
+++ b/src/App/Fossa/Config/Common.hs
@@ -10,6 +10,7 @@ module App.Fossa.Config.Common (
targetOpt,
baseDirArg,
metadataOpts,
+ parsePolicyOptions,
configFileOpt,
endpointOpt,
apiKeyOpt,
@@ -19,6 +20,7 @@ module App.Fossa.Config.Common (
validateFile,
validateExists,
validateApiKey,
+ validateApiKeyGeneric,
-- * CLI Collectors
collectBaseDir,
@@ -43,6 +45,8 @@ module App.Fossa.Config.Common (
endpointHelp,
fossaApiKeyHelp,
configHelp,
+ titleHelp,
+ -- Deprecation
deprecateReleaseGroupMetadata,
) where
@@ -178,40 +182,41 @@ metadataOpts =
<*> parsePolicyOptions
<*> many (strOption (applyFossaStyle <> long "project-label" <> stringToHelpDoc "Assign up to 5 labels to the project"))
<*> optional releaseGroupMetadataOpts
- where
- titleHelp :: Maybe (Doc AnsiStyle)
- titleHelp =
- Just . formatDoc $
- vsep
- [ "The title of the FOSSA project"
- , boldItalicized "Default: " <> "The project name"
- ]
- policy :: Parser Policy
- policy = PolicyName <$> (strOption (applyFossaStyle <> long "policy" <> helpDoc policyHelp))
-
- policyId :: Parser Policy
- policyId =
- PolicyId
- <$> ( option
- (readMWithError "failed to parse --policy-id, expecting int")
- (applyFossaStyle <> long "policy-id" <> helpDoc policyIdHelp)
- )
-
- parsePolicyOptions :: Parser (Maybe Policy)
- parsePolicyOptions = optional (policy <|> policyId) -- For Parsers '<|>' tries every alternative and fails if they all succeed.
- policyHelp :: Maybe (Doc AnsiStyle)
- policyHelp =
- Just . formatDoc $
- vsep
- [ "The name of the policy to assign to this project in FOSSA. Mutually excludes " <> coloredBoldItalicized Green "--policy-id" <> "."
- ]
- policyIdHelp :: Maybe (Doc AnsiStyle)
- policyIdHelp =
- Just . formatDoc $
- vsep
- [ "The id of the policy to assign to this project in FOSSA. Mutually excludes " <> coloredBoldItalicized Green "--policy" <> "."
- ]
+titleHelp :: Maybe (Doc AnsiStyle)
+titleHelp =
+ Just . formatDoc $
+ vsep
+ [ "The title of the FOSSA project"
+ , boldItalicized "Default: " <> "The project name"
+ ]
+
+policy :: Parser Policy
+policy = PolicyName <$> (strOption (applyFossaStyle <> long "policy" <> helpDoc policyHelp))
+
+policyId :: Parser Policy
+policyId =
+ PolicyId
+ <$> ( option
+ (readMWithError "failed to parse --policy-id, expecting int")
+ (applyFossaStyle <> long "policy-id" <> helpDoc policyIdHelp)
+ )
+
+parsePolicyOptions :: Parser (Maybe Policy)
+parsePolicyOptions = optional (policy <|> policyId) -- For Parsers '<|>' tries every alternative and fails if they all succeed.
+
+policyHelp :: Maybe (Doc AnsiStyle)
+policyHelp =
+ Just . formatDoc $
+ vsep
+ [ "The name of the policy to assign to this project in FOSSA. Mutually excludes " <> coloredBoldItalicized Green "--policy-id" <> "."
+ ]
+policyIdHelp :: Maybe (Doc AnsiStyle)
+policyIdHelp =
+ Just . formatDoc $
+ vsep
+ [ "The id of the policy to assign to this project in FOSSA. Mutually excludes " <> coloredBoldItalicized Green "--policy" <> "."
+ ]
releaseGroupMetadataOpts :: Parser ReleaseGroupMetadata
releaseGroupMetadataOpts =
@@ -329,6 +334,27 @@ validateApiKey maybeConfigFile EnvVars{envApiKey} CommonOpts{optAPIKey} = do
then fatalText "A FOSSA API key was specified, but it is an empty string"
else pure $ ApiKey textkey
+validateApiKeyGeneric ::
+ ( Has Diagnostics sig m
+ ) =>
+ Maybe ConfigFile ->
+ Maybe Text ->
+ Maybe Text ->
+ m ApiKey
+validateApiKeyGeneric maybeConfigFile maybeEnvApiKey maybeOptAPIKey = do
+ textkey <-
+ fromMaybeText "A FOSSA API key is required to run this command" $
+ -- API key precedence is strictly defined:
+ -- 1. Cmd-line option (rarely used, not encouraged)
+ -- 2. Config file (maybe used)
+ -- 3. Environment Variable (most common)
+ maybeOptAPIKey
+ <|> (maybeConfigFile >>= configApiKey)
+ <|> maybeEnvApiKey
+ if Data.Text.null . strip $ textkey
+ then fatalText "A FOSSA API key was specified, but it is an empty string"
+ else pure $ ApiKey textkey
+
collectApiOpts :: (Has Diagnostics sig m) => Maybe ConfigFile -> EnvVars -> CommonOpts -> m ApiOpts
collectApiOpts maybeconfig envvars globals = do
apikey <- validateApiKey maybeconfig envvars globals
diff --git a/src/App/Fossa/Config/ConfigFile.hs b/src/App/Fossa/Config/ConfigFile.hs
index 24072a5ef2..ea2f70be8b 100644
--- a/src/App/Fossa/Config/ConfigFile.hs
+++ b/src/App/Fossa/Config/ConfigFile.hs
@@ -210,10 +210,12 @@ data ConfigFile = ConfigFile
deriving (Eq, Ord, Show)
data ConfigProject = ConfigProject
- { configProjID :: Maybe Text
+ { configProjLocator :: Maybe Text
+ , configProjID :: Maybe Text
, configName :: Maybe Text
, configLink :: Maybe Text
, configTeam :: Maybe Text
+ , configTeams :: Maybe [Text]
, configJiraKey :: Maybe Text
, configUrl :: Maybe Text
, configPolicy :: Maybe Policy
@@ -313,10 +315,12 @@ instance FromJSON (Path Abs File -> ConfigFile) where
instance FromJSON ConfigProject where
parseJSON = withObject "ConfigProject" $ \obj ->
ConfigProject
- <$> obj .:? "id"
+ <$> obj .:? "locator"
+ <*> obj .:? "id"
<*> obj .:? "name"
<*> obj .:? "link"
<*> obj .:? "team"
+ <*> obj .:? "teams"
<*> obj .:? "jiraProjectKey"
<*> obj .:? "url"
<*> parsePolicy obj
diff --git a/src/App/Fossa/Config/Project.hs b/src/App/Fossa/Config/Project.hs
new file mode 100644
index 0000000000..50f95b7c91
--- /dev/null
+++ b/src/App/Fossa/Config/Project.hs
@@ -0,0 +1,63 @@
+module App.Fossa.Config.Project (
+ ProjectCommand (..),
+ ProjectConfig (..),
+ mkSubCommand,
+) where
+
+import App.Fossa.Config.ConfigFile (ConfigFile, resolveLocalConfigFile)
+import App.Fossa.Config.EnvironmentVars (EnvVars (..))
+import App.Fossa.Config.Project.Edit as Edit
+
+import App.Fossa.Subcommand (EffStack, GetCommonOpts, GetSeverity (..), SubCommand (..))
+import Control.Effect.Diagnostics (Diagnostics, Has)
+import Control.Effect.Lift (Lift)
+import Data.Aeson (ToJSON, defaultOptions, genericToEncoding, toEncoding)
+import Effect.Logger (Logger, Severity (..))
+import Effect.ReadFS (ReadFS)
+import GHC.Generics (Generic)
+import Options.Applicative (InfoMod, Parser, progDescDoc, subparser)
+import Style (formatStringToDoc)
+
+projectInfo :: InfoMod a
+projectInfo = progDescDoc $ formatStringToDoc "FOSSA project"
+
+mkSubCommand :: (ProjectConfig -> EffStack ()) -> SubCommand ProjectCommand ProjectConfig
+mkSubCommand = SubCommand "project" projectInfo projectCliParser loadConfig projectMergeOpts
+
+newtype ProjectCommand = Edit EditOpts
+
+newtype ProjectConfig = EditCfg EditConfig
+ deriving (Show, Generic)
+
+instance GetCommonOpts ProjectCommand
+
+instance ToJSON ProjectConfig where
+ toEncoding = genericToEncoding defaultOptions
+
+instance GetSeverity ProjectCommand where
+ getSeverity :: ProjectCommand -> Severity
+ getSeverity (Edit (EditOpts{debug})) = if debug then SevDebug else SevInfo
+
+projectMergeOpts ::
+ ( Has Diagnostics sig m
+ ) =>
+ Maybe ConfigFile ->
+ EnvVars ->
+ ProjectCommand ->
+ m ProjectConfig
+projectMergeOpts cfg envvars (Edit opts) = EditCfg <$> Edit.mergeOpts cfg envvars opts
+
+loadConfig ::
+ ( Has Diagnostics sig m
+ , Has Logger sig m
+ , Has ReadFS sig m
+ , Has (Lift IO) sig m
+ ) =>
+ ProjectCommand ->
+ m (Maybe ConfigFile)
+loadConfig (Edit opts) = resolveLocalConfigFile $ Edit.configOpts opts
+
+projectCliParser :: Parser ProjectCommand
+projectCliParser =
+ subparser $
+ Edit.subcommand Edit
diff --git a/src/App/Fossa/Config/Project/Edit.hs b/src/App/Fossa/Config/Project/Edit.hs
new file mode 100644
index 0000000000..447cedeb93
--- /dev/null
+++ b/src/App/Fossa/Config/Project/Edit.hs
@@ -0,0 +1,198 @@
+{-# LANGUAGE RecordWildCards #-}
+
+module App.Fossa.Config.Project.Edit (
+ EditConfig (..),
+ EditOpts (..),
+ ProjectIdentifier (..),
+ subcommand,
+ cliParser,
+ mergeOpts,
+) where
+
+import App.Fossa.Config.Common (configHelp, endpointHelp, fossaApiKeyCmdText, fossaApiKeyHelp, parsePolicyOptions, titleHelp, validateApiKeyGeneric)
+import App.Fossa.Config.ConfigFile (ConfigFile (..), ConfigProject (..))
+import App.Fossa.Config.EnvironmentVars (EnvVars (envApiKey))
+import App.OptionExtensions (uriOption)
+import App.Types (Policy (..))
+import Control.Effect.Diagnostics (Diagnostics, Has, fatalText)
+import Data.Aeson (ToJSON, defaultOptions, genericToEncoding, toEncoding)
+import Data.Text (Text)
+import Data.Text qualified
+import Effect.Logger (vsep)
+import Fossa.API.Types (ApiOpts (..), defaultApiPollDelay)
+import GHC.Generics (Generic)
+import Options.Applicative (
+ CommandFields,
+ InfoMod,
+ Mod,
+ Parser,
+ command,
+ helpDoc,
+ info,
+ long,
+ metavar,
+ optional,
+ short,
+ some,
+ strOption,
+ switch,
+ (<|>),
+ )
+import Options.Applicative.Builder (progDescDoc)
+import Options.Applicative.Help (AnsiStyle)
+import Prettyprinter (Doc)
+import Style (applyFossaStyle, boldItalicized, formatDoc, formatStringToDoc, stringToHelpDoc)
+import Text.URI (URI, mkURI)
+
+projectEditInfo :: InfoMod a
+projectEditInfo = progDescDoc $ formatStringToDoc "Edit a FOSSA project"
+
+subcommand :: (EditOpts -> a) -> Mod CommandFields a
+subcommand f = command "edit" $ info (f <$> cliParser) projectEditInfo
+
+data EditConfig = EditConfig
+ { apiOpts :: ApiOpts
+ , projectIdentifier :: ProjectIdentifier
+ , projectTitle :: Maybe Text
+ , projectUrl :: Maybe Text
+ , projectJiraKey :: Maybe Text
+ , projectPolicy :: Maybe Policy
+ , projectLink :: Maybe Text
+ , projectLabels :: Maybe [Text]
+ , -- Teams to add project
+ teams :: Maybe [Text]
+ }
+ deriving (Eq, Ord, Show, Generic)
+
+instance ToJSON EditConfig where
+ toEncoding = genericToEncoding defaultOptions
+
+data ProjectIdentifier
+ = ProjectId Text
+ | ProjectLocator Text
+ deriving (Eq, Ord, Show, Generic)
+
+instance ToJSON ProjectIdentifier where
+ toEncoding = genericToEncoding defaultOptions
+
+data EditOpts = EditOpts
+ { debug :: Bool
+ , baseUrlOpts :: Maybe URI
+ , apiKeyOpts :: Maybe Text
+ , configOpts :: Maybe FilePath
+ , projectIdentiferOpts :: Maybe ProjectIdentifier
+ , projectTitleOpts :: Maybe Text
+ , projectUrlOpts :: Maybe Text
+ , projectJiraKeyOpts :: Maybe Text
+ , projectPolicyOpts :: Maybe Policy
+ , projectLinkOpts :: Maybe Text
+ , projectLabelsOpts :: Maybe [Text]
+ , teamsOpts :: Maybe [Text]
+ }
+ deriving (Eq, Ord, Show, Generic)
+
+cliParser :: Parser EditOpts
+cliParser =
+ EditOpts
+ <$> switch (applyFossaStyle <> long "debug" <> stringToHelpDoc "Enable debug logging")
+ <*> optional (uriOption (applyFossaStyle <> long "endpoint" <> short 'e' <> metavar "URL" <> helpDoc endpointHelp))
+ <*> optional (strOption (applyFossaStyle <> long fossaApiKeyCmdText <> helpDoc fossaApiKeyHelp))
+ <*> optional (strOption (applyFossaStyle <> long "config" <> short 'c' <> helpDoc configHelp))
+ <*> projectIdentiferOptions
+ <*> optional (strOption (applyFossaStyle <> long "title" <> short 't' <> helpDoc titleHelp))
+ <*> optional (strOption (applyFossaStyle <> long "project-url" <> short 'P' <> stringToHelpDoc "The url of the project's repository."))
+ <*> optional (strOption (applyFossaStyle <> long "jira-project-key" <> short 'j' <> stringToHelpDoc "The JIRA project key to associate with the project."))
+ <*> parsePolicyOptions
+ <*> optional (strOption (applyFossaStyle <> long "link" <> short 'L' <> stringToHelpDoc "A link to attach to the project."))
+ <*> optional (some (strOption (applyFossaStyle <> long "project-label" <> stringToHelpDoc "Assign up to 5 labels to the project.")))
+ <*> optional (some (strOption (applyFossaStyle <> long "team" <> short 'T' <> stringToHelpDoc "The teams to associate with the project.")))
+
+projectIdHelp :: Maybe (Doc AnsiStyle)
+projectIdHelp =
+ Just . formatDoc $
+ vsep
+ [ "The project ID defines an ID that is used to reference a project within your FOSSA organization."
+ , ""
+ , "If the project ID was not explicitly set during the time of project creation, it defaults to:"
+ , boldItalicized "Git: " <> "The .git/config file or project's remote `origin` URL"
+ , boldItalicized "SVN: " <> "The `Repository Root` obtained using 'svn info'"
+ , boldItalicized "No VCS: " <> "The name of the project's directory"
+ ]
+
+projectIdOptions :: Parser ProjectIdentifier
+projectIdOptions = ProjectId <$> (strOption (applyFossaStyle <> long "project-id" <> helpDoc projectIdHelp))
+
+projectLocatorOptions :: Parser ProjectIdentifier
+projectLocatorOptions = ProjectLocator <$> (strOption (applyFossaStyle <> long "project-locator" <> stringToHelpDoc "The project locator defines a unique ID that the FOSSA API will use to reference this project within FOSSA."))
+
+projectIdentiferOptions :: Parser (Maybe ProjectIdentifier)
+projectIdentiferOptions = optional (projectIdOptions <|> projectLocatorOptions)
+
+mergeOpts ::
+ ( Has Diagnostics sig m
+ ) =>
+ Maybe ConfigFile ->
+ EnvVars ->
+ EditOpts ->
+ m EditConfig
+mergeOpts maybeConfig envVars cliOpts@EditOpts{..} = do
+ apiOpts <- collectApiOpts maybeConfig envVars cliOpts
+ maybePolicy <- maybe (pure projectPolicyOpts) (mergePolicy projectPolicyOpts) maybeConfig
+ let maybeProjectIdentifer = maybe projectIdentiferOpts (mergeProjectIdentifier projectIdentiferOpts) maybeConfig
+
+ case maybeProjectIdentifer of
+ Just projId@(ProjectId projectId')
+ | not (Data.Text.null projectId') ->
+ pure $ mkConfig apiOpts projId maybePolicy
+ Just projId@(ProjectLocator locatorVal)
+ | not (Data.Text.null locatorVal) ->
+ pure $ mkConfig apiOpts projId maybePolicy
+ _ -> fatalText "Either projectId or project locator needs to be specified to edit a FOSSA project"
+ where
+ mkConfig :: ApiOpts -> ProjectIdentifier -> Maybe Policy -> EditConfig
+ mkConfig apiOpts projectIdentifer maybePolicy = do
+ let maybeProjectConfig = maybeConfig >>= configProject
+ configProjectLabels = maybe [] configLabel maybeProjectConfig
+ EditConfig
+ { apiOpts = apiOpts
+ , projectIdentifier = projectIdentifer
+ , projectTitle = projectTitleOpts <|> extractProjectConfigVal maybeConfig configName
+ , projectUrl = projectUrlOpts <|> extractProjectConfigVal maybeConfig configUrl
+ , projectJiraKey = projectJiraKeyOpts <|> extractProjectConfigVal maybeConfig configJiraKey
+ , projectPolicy = maybePolicy
+ , projectLink = projectLinkOpts <|> extractProjectConfigVal maybeConfig configLink
+ , projectLabels = projectLabelsOpts <|> labelConfigToMaybe configProjectLabels
+ , -- Teams to add project to.
+ -- Precdence for teams is CLI input, `project.teams` field from config, then `project.team` field from config.
+ -- Account for `project.team` for backwards compatability.
+ teams = teamsOpts <|> extractProjectConfigVal maybeConfig configTeams <|> ((: []) <$> extractProjectConfigVal maybeConfig configTeam)
+ }
+
+ labelConfigToMaybe :: [Text] -> Maybe [Text]
+ labelConfigToMaybe [] = Nothing
+ labelConfigToMaybe xs = Just xs
+
+collectApiOpts :: (Has Diagnostics sig m) => Maybe ConfigFile -> EnvVars -> EditOpts -> m ApiOpts
+collectApiOpts maybeConfig envVars EditOpts{..} = do
+ apikey <- validateApiKeyGeneric maybeConfig (envApiKey envVars) apiKeyOpts
+ let configUri = maybeConfig >>= configServer >>= mkURI
+ baseuri = baseUrlOpts <|> configUri
+ pure $ ApiOpts baseuri apikey defaultApiPollDelay
+
+extractProjectConfigVal :: Maybe ConfigFile -> (ConfigProject -> Maybe a) -> Maybe a
+extractProjectConfigVal maybeConfig getValue = do
+ let projectCfg = maybeConfig >>= configProject
+ projectCfg >>= getValue
+
+mergeProjectIdentifier :: Maybe ProjectIdentifier -> ConfigFile -> Maybe ProjectIdentifier
+mergeProjectIdentifier maybeProjectIdentifer config = maybeProjectIdentifer <|> ProjectId <$> (configProject config >>= configProjID) <|> ProjectLocator <$> (configProject config >>= configProjLocator)
+
+mergePolicy :: Has Diagnostics sig m => Maybe Policy -> ConfigFile -> m (Maybe Policy)
+mergePolicy maybePolicy config =
+ case (maybePolicy, cfgPolicy) of
+ (Just (PolicyId _), Just (PolicyName _)) -> err
+ (Just (PolicyName _), Just (PolicyId _)) -> err
+ _ -> pure $ maybePolicy <|> cfgPolicy
+ where
+ err = fatalText "Only one of policy or policyId can be set. Check your cli options and .fossa.yml to ensure you aren't specifying both."
+ cfgPolicy = configProject config >>= configPolicy
diff --git a/src/App/Fossa/Init/.fossa.yml b/src/App/Fossa/Init/.fossa.yml
index 038447f697..ab4cc0e8b1 100644
--- a/src/App/Fossa/Init/.fossa.yml
+++ b/src/App/Fossa/Init/.fossa.yml
@@ -44,14 +44,20 @@ version: 3
# #
# # NOTE:
# # Can only be set when creating a project (running fossa analyze for the first time),
-# # Otherwise, they will be silently ignored.
+# # Otherwise, they will be silently ignored. Use fossa project edit to modify this field for existing projects.
# name: fossa-cli
#
# # Name of the team in your FOSSA organization to associate with this project.
# # NOTE:
# # Can only be set when creating a project (running fossa analyze for the first time),
-# # Otherwise, they will be silently ignored.
-# team: cli-team
+# # Otherwise, they will be silently ignored. Use fossa project edit to modify this field for existing projects.
+# team: example-team
+#
+# # Name of the teams in your FOSSA organization to associate with this project.
+# # Currently, this field is only supported through fossa project edit.
+# teams:
+# - example-team-1
+# - example-team-2
#
# # Name of the policy in your FOSSA organization to associate with this project.
# # NOTE:
@@ -62,7 +68,7 @@ version: 3
# # An external link that will appear in the FOSSA UI for this specific project.
# # NOTE:
# # Can only be set when creating a project (running fossa analyze for the first time),
-# # Otherwise, they will be silently ignored.
+# # Otherwise, they will be silently ignored. Use fossa project edit to modify this field for existing projects.
# link: fossa.com
#
# # The URL of your project that will appear in FOSSA.
@@ -74,7 +80,7 @@ version: 3
# #
# # NOTE:
# # Can only be set when creating a project (running fossa analyze for the first time),
-# # Otherwise, they will be silently ignored.
+# # Otherwise, they will be silently ignored. Use fossa project edit to modify this field for existing projects.
# jiraProjectKey: jira-key
#
# # The 'name' and 'release' of the release group's release to add your project to in the FOSSA dashboard.
diff --git a/src/App/Fossa/Main.hs b/src/App/Fossa/Main.hs
index e8fcf3bc42..4bef13c722 100644
--- a/src/App/Fossa/Main.hs
+++ b/src/App/Fossa/Main.hs
@@ -8,6 +8,7 @@ import App.Fossa.DumpBinaries qualified as Dump
import App.Fossa.Init (initCommand)
import App.Fossa.LicenseScan qualified as LicenseScan (licenseScanSubCommand)
import App.Fossa.ListTargets qualified as ListTargets
+import App.Fossa.Project qualified as Project
import App.Fossa.ReleaseGroup qualified as ReleaseGroup
import App.Fossa.Report qualified as Report
import App.Fossa.Snippets qualified as Snippets
@@ -103,6 +104,7 @@ subcommands = public <|> private
, decodeSubCommand Snippets.snippetsSubCommand
, initCommand
, feedbackCommand
+ , decodeSubCommand Project.projectSubCommand
, decodeSubCommand ReleaseGroup.releaseGroupSubCommand
]
diff --git a/src/App/Fossa/Project.hs b/src/App/Fossa/Project.hs
new file mode 100644
index 0000000000..37cd25910a
--- /dev/null
+++ b/src/App/Fossa/Project.hs
@@ -0,0 +1,30 @@
+module App.Fossa.Project (
+ projectSubCommand,
+) where
+
+import App.Fossa.Config.Project (ProjectCommand (..), ProjectConfig (..), mkSubCommand)
+import App.Fossa.Config.Project.Edit qualified as Edit
+import App.Fossa.Project.Edit (editMain)
+import App.Fossa.Subcommand (SubCommand)
+import Control.Algebra (Has)
+import Control.Carrier.Debug (ignoreDebug)
+import Control.Carrier.Diagnostics (context)
+import Control.Carrier.FossaApiClient (runFossaApiClient)
+import Control.Carrier.StickyLogger (runStickyLogger)
+import Control.Effect.Diagnostics (Diagnostics)
+import Control.Effect.Lift (Lift)
+import Effect.Logger (Logger, Severity (SevInfo), logInfo)
+
+projectSubCommand :: SubCommand ProjectCommand ProjectConfig
+projectSubCommand = mkSubCommand projectMain
+
+projectMain ::
+ ( Has (Lift IO) sig m
+ , Has Diagnostics sig m
+ , Has Logger sig m
+ ) =>
+ ProjectConfig ->
+ m ()
+projectMain (EditCfg config) = do
+ logInfo "Running FOSSA project"
+ context "Add projects to release group" . runStickyLogger SevInfo . ignoreDebug . runFossaApiClient (Edit.apiOpts config) $ editMain config
diff --git a/src/App/Fossa/Project/Edit.hs b/src/App/Fossa/Project/Edit.hs
new file mode 100644
index 0000000000..070955c483
--- /dev/null
+++ b/src/App/Fossa/Project/Edit.hs
@@ -0,0 +1,170 @@
+{-# LANGUAGE RecordWildCards #-}
+
+module App.Fossa.Project.Edit (
+ editMain,
+) where
+
+import App.Fossa.ApiUtils (retrieveLabelIds, retrievePolicyId, retrieveTeamIds)
+import App.Fossa.Config.Project.Edit (EditConfig (..), ProjectIdentifier (..))
+import App.Types (Policy (..))
+import Control.Algebra (Has)
+import Control.Carrier.Diagnostics (context, errHelp)
+import Control.Carrier.StickyLogger (logSticky)
+import Control.Effect.Diagnostics (Diagnostics, fatalText, warn)
+import Control.Effect.FossaApiClient (FossaApiClient, addTeamProjects, getOrgLabels, getOrganization, getPolicies, getProjectV2, getTeams, updateProject, updateRevision)
+import Control.Effect.Lift (Lift)
+import Control.Effect.StickyLogger (StickyLogger)
+import Control.Monad (void)
+import Data.Foldable (traverse_)
+import Data.Maybe (isJust)
+import Data.String.Conversion (toText)
+import Data.Text (Text, intercalate)
+import Effect.Logger (Logger, logDebug, logInfo, logStdout, pretty)
+import Fossa.API.CoreTypes (AddTeamProjectsRequest (..), AddTeamProjectsResponse (teamProjectLocators), PolicyType (LICENSING), Project (..), TeamProjectAction (Add), UpdateProjectRequest (..), UpdateRevisionRequest (UpdateRevisionRequest))
+import Fossa.API.Types (Organization (organizationId))
+import Text.Pretty.Simple (pShow)
+
+editMain ::
+ ( Has Diagnostics sig m
+ , Has Logger sig m
+ , Has (Lift IO) sig m
+ , Has FossaApiClient sig m
+ , Has StickyLogger sig m
+ ) =>
+ EditConfig ->
+ m ()
+editMain EditConfig{..} = do
+ logInfo "Running FOSSA project edit"
+
+ projectLocator <- case projectIdentifier of
+ ProjectLocator projectLocator -> pure projectLocator
+ ProjectId projectId -> do
+ org <- getOrganization
+ pure $ constructProjectLocatorFromProjectId org projectId
+
+ project <- getProjectV2 projectLocator
+
+ maybeWarnings <- attemptToUpdateProjectMetadata projectLocator (projectDefaultBranch project) $ projectIssueTrackerIds project
+ attemptToAddProjectToTeams projectLocator teams
+ attemptToUpdateRevisionMetadata projectLocator (projectLatestRevision project)
+
+ case maybeWarnings of
+ Nothing -> logStdout $ "Project " <> "`" <> projectLocator <> "` has been updated successfully." <> "\n"
+ Just projectUpdateWarnings -> do
+ logStdout $ "Project " <> "`" <> projectLocator <> "` has been updated with warnings. Run the command with `--debug` to view the warnings." <> "\n"
+ traverse_ warn projectUpdateWarnings
+ where
+ constructProjectLocatorFromProjectId :: Organization -> Text -> Text
+ constructProjectLocatorFromProjectId org projId = "custom+" <> toText (show $ organizationId org) <> "/" <> projId
+
+ attemptToUpdateProjectMetadata ::
+ ( Has FossaApiClient sig m
+ , Has Diagnostics sig m
+ , Has StickyLogger sig m
+ , Has Logger sig m
+ ) =>
+ Text ->
+ Maybe Text ->
+ Maybe [Text] ->
+ m (Maybe [Text])
+ attemptToUpdateProjectMetadata projectLocator maybeDefaultBranch currentIssueTrackerIds = do
+ -- Ensure that at least one project metadata field is being updated, otherwise the endpoint will throw an error
+ if (or [isJust projectTitle, isJust projectUrl, isJust projectJiraKey, isJust projectLabels, isJust projectPolicy])
+ then do
+ logSticky "Updating project metadata"
+ maybePolicyId <- case projectPolicy of
+ Nothing -> pure Nothing
+ Just policy -> case policy of
+ PolicyId policyId -> pure $ Just policyId
+ PolicyName policyName -> do
+ policies <- getPolicies
+ context "Retrieving license policy ID" $ retrievePolicyId (Just policyName) LICENSING policies
+
+ orgLabels <- getOrgLabels
+ (maybeLabelIds, maybeLabelWarnings) <- case projectLabels of
+ Nothing -> pure (Nothing, Nothing)
+ Just labels -> do
+ (labelIds, maybeLabelWarnings) <- context "Retrieving label Ids" $ retrieveLabelIds (labels) orgLabels
+ pure (Just labelIds, maybeLabelWarnings)
+
+ -- Updating the project's issue tracker ids is an all or nothing transaction.
+ -- Add the specified jira key to the current list of issue tracker ids if it isn't already present.
+ -- If it is present in the list, use the project's existing issue tracker ids to ensure nothing is overwritten.
+ let udpatedIssueTrackerIds = case projectJiraKey of
+ Nothing -> Nothing
+ Just jiraKey -> Just . updateIssueTrackerIds jiraKey =<< currentIssueTrackerIds
+ let req =
+ UpdateProjectRequest
+ { updateProjectTitle = projectTitle
+ , updateProjectUrl = projectUrl
+ , updateProjectIssueTrackerIds = udpatedIssueTrackerIds
+ , updateProjectLabelIds = maybeLabelIds
+ , updateProjectPolicyId = maybePolicyId
+ , -- This field needs to be set, otherwise the default branch will be removed
+ updateProjectDefaultBranch = maybeDefaultBranch
+ }
+ res <- updateProject projectLocator req
+ logDebug $ "Updated project: " <> pretty (pShow res)
+ pure maybeLabelWarnings
+ else pure Nothing
+
+ updateIssueTrackerIds :: Text -> [Text] -> [Text]
+ updateIssueTrackerIds jiraKey currentIssueTrackerIds =
+ if jiraKey `elem` currentIssueTrackerIds
+ then currentIssueTrackerIds
+ else currentIssueTrackerIds ++ [jiraKey]
+
+ attemptToUpdateRevisionMetadata ::
+ ( Has FossaApiClient sig m
+ , Has Diagnostics sig m
+ , Has StickyLogger sig m
+ ) =>
+ Text ->
+ Maybe Text ->
+ m ()
+ attemptToUpdateRevisionMetadata projectLocator maybeRevisionLocator = do
+ case projectLink of
+ Nothing -> pure ()
+ Just projectLink' -> case maybeRevisionLocator of
+ Nothing ->
+ errHelp ("Ensure your project has completed a scan in order to update the revision link" :: Text) $
+ fatalText $
+ "No revision found for project: `" <> projectLocator <> "`"
+ Just revisionLocator -> do
+ logSticky "Updating revision metadata"
+ void $ updateRevision revisionLocator $ UpdateRevisionRequest projectLink'
+
+ attemptToAddProjectToTeams ::
+ ( Has FossaApiClient sig m
+ , Has Diagnostics sig m
+ , Has Logger sig m
+ , Has StickyLogger sig m
+ ) =>
+ Text ->
+ Maybe [Text] ->
+ m ()
+ attemptToAddProjectToTeams projectLocator maybeTeamNames = do
+ case maybeTeamNames of
+ Nothing -> pure ()
+ Just teamNames -> do
+ logSticky $ "Adding Project: `" <> projectLocator <> "` to Teams: `" <> intercalate ", " teamNames <> "`"
+ orgTeams <- getTeams
+ teamIds <- context "Retrieving team IDs" $ retrieveTeamIds teamNames orgTeams
+
+ logSticky "Teams in org:"
+ logDebug $ pretty (pShow orgTeams)
+ traverse_ (addTeamProjectWithLogs projectLocator) teamIds
+
+ addTeamProjectWithLogs ::
+ ( Has FossaApiClient sig m
+ , Has Logger sig m
+ , Has StickyLogger sig m
+ ) =>
+ Text ->
+ Int ->
+ m ()
+ addTeamProjectWithLogs projectLocator teamId = do
+ res <- addTeamProjects teamId $ AddTeamProjectsRequest [projectLocator] Add
+ logSticky $ "Project: `" <> projectLocator <> "` added to teamId: `" <> toText teamId <> "`"
+ logSticky $ "All projects in teamID: `" <> toText teamId <> "`"
+ logDebug $ pretty (pShow (teamProjectLocators res))
diff --git a/src/App/Fossa/ReleaseGroup/AddProjects.hs b/src/App/Fossa/ReleaseGroup/AddProjects.hs
index d6b23302d2..4de3043d90 100644
--- a/src/App/Fossa/ReleaseGroup/AddProjects.hs
+++ b/src/App/Fossa/ReleaseGroup/AddProjects.hs
@@ -16,7 +16,7 @@ import Data.Set qualified as Set
import Data.String.Conversion (ToText (..))
import Data.Text (Text)
import Effect.Logger (Logger, logDebug, logInfo, logStdout, pretty)
-import Fossa.API.Types (ReleaseGroupRelease (..), ReleaseProject (..), UpdateReleaseProjectRequest (..), UpdateReleaseRequest (..))
+import Fossa.API.CoreTypes (ReleaseGroupRelease (..), ReleaseProject (..), UpdateReleaseProjectRequest (..), UpdateReleaseRequest (..))
import Text.Pretty.Simple (pShow)
addProjectsMain ::
diff --git a/src/App/Fossa/ReleaseGroup/Common.hs b/src/App/Fossa/ReleaseGroup/Common.hs
index 6dec089130..b7f629d68b 100644
--- a/src/App/Fossa/ReleaseGroup/Common.hs
+++ b/src/App/Fossa/ReleaseGroup/Common.hs
@@ -6,7 +6,7 @@ module App.Fossa.ReleaseGroup.Common (
import Control.Algebra (Has)
import Control.Effect.Diagnostics (Diagnostics, errHelp, fatalText)
import Data.Text (Text)
-import Fossa.API.Types (ReleaseGroup (..), ReleaseGroupRelease (..))
+import Fossa.API.CoreTypes (ReleaseGroup (..), ReleaseGroupRelease (..))
retrieveReleaseGroupId :: Has Diagnostics sig m => Text -> [ReleaseGroup] -> m (Maybe Int)
retrieveReleaseGroupId title releaseGroups = do
diff --git a/src/App/Fossa/ReleaseGroup/Create.hs b/src/App/Fossa/ReleaseGroup/Create.hs
index 0eeb29288e..9866b7cb69 100644
--- a/src/App/Fossa/ReleaseGroup/Create.hs
+++ b/src/App/Fossa/ReleaseGroup/Create.hs
@@ -2,10 +2,9 @@
module App.Fossa.ReleaseGroup.Create (
createMain,
- retrievePolicyId,
- retrieveTeamIds,
) where
+import App.Fossa.ApiUtils (retrievePolicyId, retrieveTeamIdsWithMaybe)
import App.Fossa.Config.ReleaseGroup.Create (CreateConfig (..))
import App.Fossa.ReleaseGroup.Common (retrieveReleaseGroupId)
import App.Types (ReleaseGroupRevision (..))
@@ -14,12 +13,11 @@ import Control.Effect.Diagnostics (Diagnostics, context, errHelp, fatalText)
import Control.Effect.FossaApiClient (FossaApiClient, createReleaseGroup, getPolicies, getReleaseGroups, getTeams)
import Control.Effect.Lift (Lift)
import Control.Monad (when)
-import Data.Map qualified as Map
-import Data.Maybe (isJust, mapMaybe)
+import Data.Maybe (isJust)
import Data.String.Conversion (ToText (..))
-import Data.Text (Text, intercalate)
+import Data.Text (Text)
import Effect.Logger (Logger, logInfo, logStdout)
-import Fossa.API.Types (CreateReleaseGroupRequest (..), CreateReleaseGroupResponse (..), Policy (..), PolicyType (..), Team (..))
+import Fossa.API.CoreTypes (CreateReleaseGroupRequest (..), CreateReleaseGroupResponse (..), PolicyType (..))
createMain ::
( Has Diagnostics sig m
@@ -47,7 +45,7 @@ createMain CreateConfig{..} = do
maybeLicensePolicyId <- context "Retrieving license policy ID" $ retrievePolicyId (releaseGroupLicensePolicy releaseGroupRevision) LICENSING policies
maybeSecurityPolicyId <- context "Retrieving security policy ID" $ retrievePolicyId (releaseGroupSecurityPolicy releaseGroupRevision) SECURITY policies
maybeQualityPolicyId <- context "Retrieving quality policy ID" $ retrievePolicyId (releaseGroupQualityPolicy releaseGroupRevision) QUALITY policies
- maybeTeamIds <- context "Retrieving team IDs" $ retrieveTeamIds (releaseGroupTeams releaseGroupRevision) teams
+ maybeTeamIds <- context "Retrieving team IDs" $ retrieveTeamIdsWithMaybe (releaseGroupTeams releaseGroupRevision) teams
let req = CreateReleaseGroupRequest (releaseGroupTitle releaseGroupRevision) (releaseGroupReleaseRevision releaseGroupRevision) maybeLicensePolicyId maybeSecurityPolicyId maybeQualityPolicyId maybeTeamIds
res <- createReleaseGroup req
@@ -56,29 +54,3 @@ createMain CreateConfig{..} = do
where
createdReleaseGroupLink :: Int -> Text
createdReleaseGroupLink releaseGroupId = "https://app.fossa.com/projects/group/" <> toText releaseGroupId
-
-retrievePolicyId :: Has Diagnostics sig m => Maybe Text -> PolicyType -> [Policy] -> m (Maybe Int)
-retrievePolicyId maybeTitle targetType policies = case maybeTitle of
- Nothing -> pure Nothing
- Just targetTitle -> do
- let filteredPolicies = filter (\p -> policyTitle p == targetTitle && (policyType p == targetType)) policies
- case filteredPolicies of
- [] -> fatalText $ "Policy `" <> targetTitle <> "` not found"
- [policy] -> pure . Just $ policyId policy
- (_ : _ : _) ->
- errHelp ("Navigate to the FOSSA web UI to rename your policies so that they are unqiue" :: Text)
- . fatalText
- $ "Multiple policies with title `" <> targetTitle <> "` found. Unable to determine which policy to use."
-
-retrieveTeamIds :: Has Diagnostics sig m => Maybe [Text] -> [Team] -> m (Maybe [Int])
-retrieveTeamIds maybeTeamNames teams = case maybeTeamNames of
- Nothing -> pure Nothing
- Just teamNames -> do
- let teamMap = Map.fromList $ map (\team -> (teamName team, teamId team)) teams
- validTeamIds = mapMaybe (`Map.lookup` teamMap) teamNames
-
- if length teamNames == length validTeamIds
- then pure . Just $ validTeamIds
- else do
- let missingTeamNames = filter (`Map.notMember` teamMap) teamNames
- fatalText $ "Teams " <> intercalate "," missingTeamNames <> "not found"
diff --git a/src/App/Fossa/ReleaseGroup/CreateRelease.hs b/src/App/Fossa/ReleaseGroup/CreateRelease.hs
index 380059f2e6..441753ca0d 100644
--- a/src/App/Fossa/ReleaseGroup/CreateRelease.hs
+++ b/src/App/Fossa/ReleaseGroup/CreateRelease.hs
@@ -13,7 +13,7 @@ import Control.Effect.Lift (Lift)
import Data.String.Conversion (ToText (..))
import Data.Text (Text)
import Effect.Logger (Logger, logInfo, logStdout)
-import Fossa.API.Types (ReleaseGroupRelease (..))
+import Fossa.API.CoreTypes (ReleaseGroupRelease (..))
createReleaseMain ::
( Has Diagnostics sig m
diff --git a/src/App/Fossa/ReleaseGroup/DeleteRelease.hs b/src/App/Fossa/ReleaseGroup/DeleteRelease.hs
index 96fb87e007..e002332e52 100644
--- a/src/App/Fossa/ReleaseGroup/DeleteRelease.hs
+++ b/src/App/Fossa/ReleaseGroup/DeleteRelease.hs
@@ -12,7 +12,7 @@ import Control.Effect.FossaApiClient (FossaApiClient, deleteReleaseGroupRelease,
import Control.Effect.Lift (Lift)
import Control.Monad (when)
import Effect.Logger (Logger, logInfo, logStdout)
-import Fossa.API.Types (ReleaseGroupRelease (..))
+import Fossa.API.CoreTypes (ReleaseGroupRelease (..))
deleteReleaseMain ::
( Has Diagnostics sig m
diff --git a/src/Control/Carrier/FossaApiClient.hs b/src/Control/Carrier/FossaApiClient.hs
index ce10440e76..efc42d9e0c 100644
--- a/src/Control/Carrier/FossaApiClient.hs
+++ b/src/Control/Carrier/FossaApiClient.hs
@@ -47,6 +47,7 @@ runFossaApiClient apiOpts =
GetPolicies -> Core.getPolicies
GetProject rev -> Core.getProject rev
GetTeams -> Core.getTeams
+ AddTeamProjects teamId req -> Core.addTeamProjects teamId req
GetAnalyzedRevisions vdeps -> Core.getAnalyzedRevisions vdeps
GetSignedFirstPartyScanUrl rev -> LicenseScanning.getSignedFirstPartyScanUrl rev
GetSignedLicenseScanUrl rev -> LicenseScanning.getSignedLicenseScanUrl rev
@@ -77,5 +78,12 @@ runFossaApiClient apiOpts =
GetReleaseGroups -> Core.getReleaseGroups
GetReleaseGroupReleases releaseGroupId -> Core.getReleaseGroupReleases releaseGroupId
CreateReleaseGroup req -> Core.createReleaseGroup req
+ -- Project
+ GetProjectV2 locator -> Core.getProjectV2 locator
+ UpdateProject locator req -> Core.updateProject locator req
+ -- Revision
+ UpdateRevision revisionLocator req -> Core.updateRevision revisionLocator req
+ -- Labels
+ GetOrgLabels -> Core.getOrgLabels
CreateReleaseGroupRelease releaseGroupId req -> Core.createReleaseGroupRelease releaseGroupId req
)
diff --git a/src/Control/Carrier/FossaApiClient/Internal/Core.hs b/src/Control/Carrier/FossaApiClient/Internal/Core.hs
index 0fc7505533..8a35f96f95 100644
--- a/src/Control/Carrier/FossaApiClient/Internal/Core.hs
+++ b/src/Control/Carrier/FossaApiClient/Internal/Core.hs
@@ -29,6 +29,11 @@ module Control.Carrier.FossaApiClient.Internal.Core (
getReleaseGroups,
getReleaseGroupReleases,
updateReleaseGroupRelease,
+ getProjectV2,
+ updateProject,
+ addTeamProjects,
+ updateRevision,
+ getOrgLabels,
) where
import App.Fossa.Config.Report (ReportOutputFormat)
@@ -54,22 +59,18 @@ import Fossa.API.Types (
Archive,
Build,
Contributors,
- CreateReleaseGroupRequest,
- CreateReleaseGroupResponse,
CustomBuildUploadPermissions,
Issues,
Organization,
- Policy,
Project,
- ReleaseGroup,
- ReleaseGroupRelease,
RevisionDependencyCache,
SignedURL,
- Team,
TokenTypeResponse,
- UpdateReleaseRequest,
UploadResponse,
)
+
+import Fossa.API.CoreTypes qualified as CoreTypes
+
import Srclib.Types (Locator, SourceUnit, renderLocator)
-- Fetches an organization from the API
@@ -319,8 +320,8 @@ createReleaseGroup ::
, Has Debug sig m
, Has (Reader ApiOpts) sig m
) =>
- CreateReleaseGroupRequest ->
- m CreateReleaseGroupResponse
+ CoreTypes.CreateReleaseGroupRequest ->
+ m CoreTypes.CreateReleaseGroupResponse
createReleaseGroup req = do
apiOpts <- ask
API.createReleaseGroup apiOpts req
@@ -333,7 +334,7 @@ createReleaseGroupRelease ::
) =>
Int ->
ReleaseGroupReleaseRevision ->
- m ReleaseGroupRelease
+ m CoreTypes.ReleaseGroupRelease
createReleaseGroupRelease releaseGroupId req = do
apiOpts <- ask
API.createReleaseGroupRelease apiOpts releaseGroupId req
@@ -344,7 +345,7 @@ getPolicies ::
, Has Debug sig m
, Has (Reader ApiOpts) sig m
) =>
- m [Policy]
+ m [CoreTypes.Policy]
getPolicies = do
apiOpts <- ask
API.getPolicies apiOpts
@@ -355,11 +356,24 @@ getTeams ::
, Has Debug sig m
, Has (Reader ApiOpts) sig m
) =>
- m [Team]
+ m [CoreTypes.Team]
getTeams = do
apiOpts <- ask
API.getTeams apiOpts
+addTeamProjects ::
+ ( Has (Lift IO) sig m
+ , Has Diagnostics sig m
+ , Has Debug sig m
+ , Has (Reader ApiOpts) sig m
+ ) =>
+ Int ->
+ CoreTypes.AddTeamProjectsRequest ->
+ m CoreTypes.AddTeamProjectsResponse
+addTeamProjects teamId req = do
+ apiOpts <- ask
+ API.addTeamProjects apiOpts teamId req
+
deleteReleaseGroup ::
( Has (Lift IO) sig m
, Has Diagnostics sig m
@@ -393,8 +407,8 @@ updateReleaseGroupRelease ::
) =>
Int ->
Int ->
- UpdateReleaseRequest ->
- m ReleaseGroupRelease
+ CoreTypes.UpdateReleaseRequest ->
+ m CoreTypes.ReleaseGroupRelease
updateReleaseGroupRelease releaseGroupId releaseId updateReq = do
apiOpts <- ask
API.updateReleaseGroupRelease apiOpts releaseGroupId releaseId updateReq
@@ -405,7 +419,7 @@ getReleaseGroups ::
, Has Debug sig m
, Has (Reader ApiOpts) sig m
) =>
- m [ReleaseGroup]
+ m [CoreTypes.ReleaseGroup]
getReleaseGroups = do
apiOpts <- ask
API.getReleaseGroups apiOpts
@@ -417,7 +431,56 @@ getReleaseGroupReleases ::
, Has (Reader ApiOpts) sig m
) =>
Int ->
- m [ReleaseGroupRelease]
+ m [CoreTypes.ReleaseGroupRelease]
getReleaseGroupReleases releaseGroupId = do
apiOpts <- ask
API.getReleaseGroupReleases apiOpts releaseGroupId
+
+getProjectV2 ::
+ ( Has (Lift IO) sig m
+ , Has Diagnostics sig m
+ , Has Debug sig m
+ , Has (Reader ApiOpts) sig m
+ ) =>
+ Text ->
+ m CoreTypes.Project
+getProjectV2 locator = do
+ apiOpts <- ask
+ API.getProjectV2 apiOpts locator
+
+updateProject ::
+ ( Has (Lift IO) sig m
+ , Has Diagnostics sig m
+ , Has Debug sig m
+ , Has (Reader ApiOpts) sig m
+ ) =>
+ Text ->
+ CoreTypes.UpdateProjectRequest ->
+ m CoreTypes.Project
+updateProject locator req = do
+ apiOpts <- ask
+ API.updateProject apiOpts locator req
+
+updateRevision ::
+ ( Has (Lift IO) sig m
+ , Has Diagnostics sig m
+ , Has Debug sig m
+ , Has (Reader ApiOpts) sig m
+ ) =>
+ Text ->
+ CoreTypes.UpdateRevisionRequest ->
+ m CoreTypes.Revision
+updateRevision revisionLocator req = do
+ apiOpts <- ask
+ API.updateRevision apiOpts revisionLocator req
+
+getOrgLabels ::
+ ( Has (Lift IO) sig m
+ , Has Diagnostics sig m
+ , Has Debug sig m
+ , Has (Reader ApiOpts) sig m
+ ) =>
+ m CoreTypes.Labels
+getOrgLabels = do
+ apiOpts <- ask
+ API.getOrgLabels apiOpts
diff --git a/src/Control/Carrier/FossaApiClient/Internal/FossaAPIV1.hs b/src/Control/Carrier/FossaApiClient/Internal/FossaAPIV1.hs
index 3126640fa6..862f937da6 100644
--- a/src/Control/Carrier/FossaApiClient/Internal/FossaAPIV1.hs
+++ b/src/Control/Carrier/FossaApiClient/Internal/FossaAPIV1.hs
@@ -63,6 +63,17 @@ module Control.Carrier.FossaApiClient.Internal.FossaAPIV1 (
-- * Team
getTeams,
+ addTeamProjects,
+
+ -- * Project
+ updateProject,
+ getProjectV2,
+
+ -- * Revision
+ updateRevision,
+
+ -- * Label
+ getOrgLabels,
) where
import App.Docs (fossaSslCertDocsUrl)
@@ -151,8 +162,6 @@ import Fossa.API.Types (
ArchiveComponents (ArchiveComponents),
Build,
Contributors,
- CreateReleaseGroupRequest,
- CreateReleaseGroupResponse,
CustomBuildUploadPermissions,
Issues,
OrgId,
@@ -160,19 +169,16 @@ import Fossa.API.Types (
PathDependencyFinalizeReq (..),
PathDependencyUpload,
PathDependencyUploadReq (..),
- Policy (..),
Project,
- ReleaseGroup,
- ReleaseGroupRelease,
RevisionDependencyCache,
SignedURL (signedURL),
SignedURLWithKey (surlwkKey, surlwkSignedURL),
- Team,
TokenTypeResponse,
- UpdateReleaseRequest,
UploadResponse,
useApiOpts,
)
+
+import Fossa.API.CoreTypes qualified as CoreTypes
import Network.HTTP.Client (responseStatus)
import Network.HTTP.Client qualified as C
import Network.HTTP.Client qualified as HTTP
@@ -185,6 +191,7 @@ import Network.HTTP.Req (
MonadHttp (..),
NoReqBody (NoReqBody),
Option,
+ PATCH (PATCH),
POST (POST),
PUT (PUT),
ReqBodyBs (ReqBodyBs),
@@ -1634,7 +1641,7 @@ getPolicies ::
, Has Debug sig m
) =>
ApiOpts ->
- m [Fossa.API.Types.Policy]
+ m [CoreTypes.Policy]
getPolicies apiOpts = fossaReq $ do
(baseUrl, baseOpts) <- useApiOpts apiOpts
resp <-
@@ -1651,7 +1658,7 @@ getTeams ::
, Has Debug sig m
) =>
ApiOpts ->
- m [Fossa.API.Types.Team]
+ m [CoreTypes.Team]
getTeams apiOpts = fossaReq $ do
(baseUrl, baseOpts) <- useApiOpts apiOpts
resp <-
@@ -1659,6 +1666,25 @@ getTeams apiOpts = fossaReq $ do
req GET (teamsURLEndpoint baseUrl) NoReqBody jsonResponse baseOpts
pure (responseBody resp)
+addTeamProjectsURLEndpoint :: Url 'Https -> Text -> Url 'Https
+addTeamProjectsURLEndpoint baseUrl teamId = baseUrl /: "api" /: "teams" /: teamId /: "projects"
+
+addTeamProjects ::
+ ( Has (Lift IO) sig m
+ , Has Diagnostics sig m
+ , Has Debug sig m
+ ) =>
+ ApiOpts ->
+ Int ->
+ CoreTypes.AddTeamProjectsRequest ->
+ m CoreTypes.AddTeamProjectsResponse
+addTeamProjects apiOpts teamId addProjectsReq = fossaReq $ do
+ (baseUrl, baseOpts) <- useApiOpts apiOpts
+ resp <-
+ context "Adding projects to team" $
+ req PUT (addTeamProjectsURLEndpoint baseUrl $ toText teamId) (ReqBodyJson addProjectsReq) jsonResponse baseOpts
+ pure (responseBody resp)
+
deleteReleaseGroupURLEndpoint :: Url 'Https -> Text -> Url 'Https
deleteReleaseGroupURLEndpoint baseUrl releaseGroupId = baseUrl /: "api" /: "project_group" /: releaseGroupId
@@ -1702,8 +1728,8 @@ updateReleaseGroupRelease ::
ApiOpts ->
Int ->
Int ->
- UpdateReleaseRequest ->
- m ReleaseGroupRelease
+ CoreTypes.UpdateReleaseRequest ->
+ m CoreTypes.ReleaseGroupRelease
updateReleaseGroupRelease apiOpts releaseGroupId releaseId updateReq = fossaReq $ do
(baseUrl, baseOpts) <- useApiOpts apiOpts
resp <-
@@ -1720,8 +1746,8 @@ createReleaseGroup ::
, Has Debug sig m
) =>
ApiOpts ->
- CreateReleaseGroupRequest ->
- m CreateReleaseGroupResponse
+ CoreTypes.CreateReleaseGroupRequest ->
+ m CoreTypes.CreateReleaseGroupResponse
createReleaseGroup apiOpts createReleaseGroupReq = fossaReq $ do
(baseUrl, baseOpts) <- useApiOpts apiOpts
resp <-
@@ -1735,7 +1761,7 @@ getReleaseGroups ::
, Has Debug sig m
) =>
ApiOpts ->
- m [ReleaseGroup]
+ m [CoreTypes.ReleaseGroup]
getReleaseGroups apiOpts = fossaReq $ do
(baseUrl, baseOpts) <- useApiOpts apiOpts
resp <-
@@ -1753,7 +1779,7 @@ getReleaseGroupReleases ::
) =>
ApiOpts ->
Int ->
- m [ReleaseGroupRelease]
+ m [CoreTypes.ReleaseGroupRelease]
getReleaseGroupReleases apiOpts releaseGroupId = fossaReq $ do
(baseUrl, baseOpts) <- useApiOpts apiOpts
resp <-
@@ -1769,10 +1795,84 @@ createReleaseGroupRelease ::
ApiOpts ->
Int ->
ReleaseGroupReleaseRevision ->
- m ReleaseGroupRelease
+ m CoreTypes.ReleaseGroupRelease
createReleaseGroupRelease apiOpts releaseGroupId createReleaseReq = fossaReq $ do
(baseUrl, baseOpts) <- useApiOpts apiOpts
resp <-
context "Creating release group release" $
req POST (releaseGroupReleaseURLEndpoint baseUrl $ toText releaseGroupId) (ReqBodyJson createReleaseReq) jsonResponse baseOpts
pure (responseBody resp)
+
+updateProjectURLEndpoint :: Url 'Https -> Text -> Url 'Https
+updateProjectURLEndpoint baseUrl locator = baseUrl /: "api" /: "projects" /: locator
+
+updateProject ::
+ ( Has (Lift IO) sig m
+ , Has Diagnostics sig m
+ , Has Debug sig m
+ ) =>
+ ApiOpts ->
+ Text ->
+ CoreTypes.UpdateProjectRequest ->
+ m CoreTypes.Project
+updateProject apiOpts locator updateReq = fossaReq $ do
+ (baseUrl, baseOpts) <- useApiOpts apiOpts
+ resp <-
+ context "Updating project" $
+ req PUT (updateProjectURLEndpoint baseUrl locator) (ReqBodyJsonCompat updateReq) jsonResponse baseOpts
+ pure (responseBody resp)
+
+updateRevisionURLEndpoint :: Url 'Https -> Text -> Url 'Https
+updateRevisionURLEndpoint baseUrl revisionLocator = baseUrl /: "api" /: "revisions" /: revisionLocator
+
+updateRevision ::
+ ( Has (Lift IO) sig m
+ , Has Diagnostics sig m
+ , Has Debug sig m
+ ) =>
+ ApiOpts ->
+ Text ->
+ CoreTypes.UpdateRevisionRequest ->
+ m CoreTypes.Revision
+updateRevision apiOpts revisionLocator updateReq = fossaReq $ do
+ (baseUrl, baseOpts) <- useApiOpts apiOpts
+ resp <-
+ context "Updating revision" $
+ req PATCH (updateRevisionURLEndpoint baseUrl revisionLocator) (ReqBodyJson updateReq) jsonResponse baseOpts
+ pure (responseBody resp)
+
+-- Get project using Core's main endpoint
+getProjectV2URLEndpoint :: Url 'Https -> Text -> Url 'Https
+getProjectV2URLEndpoint baseUrl locator = baseUrl /: "api" /: "projects" /: locator
+
+getProjectV2 ::
+ ( Has (Lift IO) sig m
+ , Has Diagnostics sig m
+ , Has Debug sig m
+ ) =>
+ ApiOpts ->
+ Text ->
+ m CoreTypes.Project
+getProjectV2 apiOpts locator = fossaReq $ do
+ (baseUrl, baseOpts) <- useApiOpts apiOpts
+ resp <-
+ context "Get project" $
+ req GET (getProjectV2URLEndpoint baseUrl locator) NoReqBody jsonResponse baseOpts
+ pure (responseBody resp)
+
+getOrgLabelsURLEndpoint :: Url 'Https -> Url 'Https
+getOrgLabelsURLEndpoint baseUrl = baseUrl /: "api" /: "organizations" /: "labels"
+
+getOrgLabels ::
+ ( Has (Lift IO) sig m
+ , Has Diagnostics sig m
+ , Has Debug sig m
+ ) =>
+ ApiOpts ->
+ m CoreTypes.Labels
+getOrgLabels apiOpts = fossaReq $ do
+ (baseUrl, baseOpts) <- useApiOpts apiOpts
+ resp <-
+ context "Retrieving organization labels" $
+ req GET (getOrgLabelsURLEndpoint baseUrl) NoReqBody jsonResponse baseOpts
+ pure (responseBody resp)
diff --git a/src/Control/Effect/FossaApiClient.hs b/src/Control/Effect/FossaApiClient.hs
index c6fbbd691f..1db6cb7127 100644
--- a/src/Control/Effect/FossaApiClient.hs
+++ b/src/Control/Effect/FossaApiClient.hs
@@ -5,6 +5,7 @@ module Control.Effect.FossaApiClient (
FossaApiClientF (..),
FossaApiClient,
addFilesToVsiScan,
+ addTeamProjects,
assertRevisionBinaries,
assertUserDefinedBinaries,
completeVsiScan,
@@ -50,6 +51,10 @@ module Control.Effect.FossaApiClient (
getReleaseGroups,
getReleaseGroupReleases,
updateReleaseGroupRelease,
+ getProjectV2,
+ updateProject,
+ updateRevision,
+ getOrgLabels,
) where
import App.Fossa.Config.Report (ReportOutputFormat)
@@ -70,7 +75,26 @@ import Data.List.NonEmpty (NonEmpty)
import Data.List.NonEmpty qualified as NE
import Data.Map (Map)
import Data.Text (Text)
-import Fossa.API.Types (AnalyzedPathDependency, ApiOpts, Archive, ArchiveComponents, Build, Contributors, CreateReleaseGroupRequest, CreateReleaseGroupResponse, CustomBuildUploadPermissions, Issues, Organization, PathDependencyUpload, Policy, Project, ReleaseGroup, ReleaseGroupRelease, RevisionDependencyCache, SignedURL, Team, TokenTypeResponse, UpdateReleaseRequest, UploadResponse)
+import Fossa.API.Types (
+ AnalyzedPathDependency,
+ ApiOpts,
+ Archive,
+ ArchiveComponents,
+ Build,
+ Contributors,
+ CustomBuildUploadPermissions,
+ Issues,
+ Organization,
+ PathDependencyUpload,
+ Project,
+ RevisionDependencyCache,
+ SignedURL,
+ TokenTypeResponse,
+ UploadResponse,
+ )
+
+import Fossa.API.CoreTypes qualified as CoreTypes
+
import Path (File, Path, Rel)
import Srclib.Types (FullSourceUnit, LicenseSourceUnit, Locator, SourceUnit)
@@ -85,6 +109,7 @@ data PackageRevision = PackageRevision
-- Note: If you add an entry here, please add a corresponding entry in @Test.MockApi.matchExpectation@.
data FossaApiClientF a where
AddFilesToVsiScan :: VSI.ScanID -> Map (Path Rel File) Fingerprint.Combined -> FossaApiClientF ()
+ AddTeamProjects :: Int -> CoreTypes.AddTeamProjectsRequest -> FossaApiClientF CoreTypes.AddTeamProjectsResponse
AssertRevisionBinaries :: Locator -> [Fingerprint Raw] -> FossaApiClientF ()
AssertUserDefinedBinaries :: IAT.UserDefinedAssertionMeta -> [Fingerprint Raw] -> FossaApiClientF ()
CompleteVsiScan :: VSI.ScanID -> FossaApiClientF ()
@@ -102,9 +127,9 @@ data FossaApiClientF a where
GetRevisionDependencyCacheStatus :: ProjectRevision -> FossaApiClientF RevisionDependencyCache
GetLatestBuild :: ProjectRevision -> FossaApiClientF Build
GetOrganization :: FossaApiClientF Organization
- GetPolicies :: FossaApiClientF [Policy]
+ GetPolicies :: FossaApiClientF [CoreTypes.Policy]
GetProject :: ProjectRevision -> FossaApiClientF Project
- GetTeams :: FossaApiClientF [Team]
+ GetTeams :: FossaApiClientF [CoreTypes.Team]
GetAnalyzedRevisions :: NonEmpty VendoredDependency -> FossaApiClientF [Text]
GetAnalyzedPathRevisions :: ProjectRevision -> FossaApiClientF [AnalyzedPathDependency]
GetSignedFirstPartyScanUrl :: PackageRevision -> FossaApiClientF SignedURL
@@ -143,11 +168,15 @@ data FossaApiClientF a where
UploadBuildForReachability :: ProjectRevision -> ProjectMetadata -> [SourceUnitReachability] -> FossaApiClientF ()
DeleteReleaseGroup :: Int -> FossaApiClientF ()
DeleteReleaseGroupRelease :: Int -> Int -> FossaApiClientF ()
- CreateReleaseGroup :: CreateReleaseGroupRequest -> FossaApiClientF CreateReleaseGroupResponse
- CreateReleaseGroupRelease :: Int -> ReleaseGroupReleaseRevision -> FossaApiClientF ReleaseGroupRelease
- UpdateReleaseGroupRelease :: Int -> Int -> UpdateReleaseRequest -> FossaApiClientF ReleaseGroupRelease
- GetReleaseGroups :: FossaApiClientF [ReleaseGroup]
- GetReleaseGroupReleases :: Int -> FossaApiClientF [ReleaseGroupRelease]
+ CreateReleaseGroup :: CoreTypes.CreateReleaseGroupRequest -> FossaApiClientF CoreTypes.CreateReleaseGroupResponse
+ CreateReleaseGroupRelease :: Int -> ReleaseGroupReleaseRevision -> FossaApiClientF CoreTypes.ReleaseGroupRelease
+ UpdateReleaseGroupRelease :: Int -> Int -> CoreTypes.UpdateReleaseRequest -> FossaApiClientF CoreTypes.ReleaseGroupRelease
+ GetReleaseGroups :: FossaApiClientF [CoreTypes.ReleaseGroup]
+ GetReleaseGroupReleases :: Int -> FossaApiClientF [CoreTypes.ReleaseGroupRelease]
+ GetProjectV2 :: Text -> FossaApiClientF CoreTypes.Project
+ UpdateProject :: Text -> CoreTypes.UpdateProjectRequest -> FossaApiClientF CoreTypes.Project
+ UpdateRevision :: Text -> CoreTypes.UpdateRevisionRequest -> FossaApiClientF CoreTypes.Revision
+ GetOrgLabels :: FossaApiClientF CoreTypes.Labels
deriving instance Show (FossaApiClientF a)
@@ -274,29 +303,44 @@ uploadContentForReachability = sendSimple . UploadContentForReachability
uploadBuildForReachability :: (Has FossaApiClient sig m) => ProjectRevision -> ProjectMetadata -> [SourceUnitReachability] -> m ()
uploadBuildForReachability rev revMetadata units = sendSimple $ UploadBuildForReachability rev revMetadata units
-getPolicies :: Has FossaApiClient sig m => m [Policy]
+getPolicies :: Has FossaApiClient sig m => m [CoreTypes.Policy]
getPolicies = sendSimple GetPolicies
-getTeams :: Has FossaApiClient sig m => m [Team]
+getTeams :: Has FossaApiClient sig m => m [CoreTypes.Team]
getTeams = sendSimple GetTeams
+addTeamProjects :: Has FossaApiClient sig m => Int -> CoreTypes.AddTeamProjectsRequest -> m CoreTypes.AddTeamProjectsResponse
+addTeamProjects teamId req = sendSimple $ AddTeamProjects teamId req
+
deleteReleaseGroup :: Has FossaApiClient sig m => Int -> m ()
deleteReleaseGroup releaseGroupId = sendSimple $ DeleteReleaseGroup releaseGroupId
deleteReleaseGroupRelease :: Has FossaApiClient sig m => Int -> Int -> m ()
deleteReleaseGroupRelease releaseGroupId releaseId = sendSimple $ DeleteReleaseGroupRelease releaseGroupId releaseId
-updateReleaseGroupRelease :: Has FossaApiClient sig m => Int -> Int -> UpdateReleaseRequest -> m ReleaseGroupRelease
+updateReleaseGroupRelease :: Has FossaApiClient sig m => Int -> Int -> CoreTypes.UpdateReleaseRequest -> m CoreTypes.ReleaseGroupRelease
updateReleaseGroupRelease releaseGroupId releaseId updateReq = sendSimple $ UpdateReleaseGroupRelease releaseGroupId releaseId updateReq
-createReleaseGroup :: Has FossaApiClient sig m => CreateReleaseGroupRequest -> m CreateReleaseGroupResponse
+createReleaseGroup :: Has FossaApiClient sig m => CoreTypes.CreateReleaseGroupRequest -> m CoreTypes.CreateReleaseGroupResponse
createReleaseGroup req = sendSimple $ CreateReleaseGroup req
-createReleaseGroupRelease :: Has FossaApiClient sig m => Int -> ReleaseGroupReleaseRevision -> m ReleaseGroupRelease
+createReleaseGroupRelease :: Has FossaApiClient sig m => Int -> ReleaseGroupReleaseRevision -> m CoreTypes.ReleaseGroupRelease
createReleaseGroupRelease releaseGroupId req = sendSimple $ CreateReleaseGroupRelease releaseGroupId req
-getReleaseGroups :: Has FossaApiClient sig m => m [ReleaseGroup]
+getReleaseGroups :: Has FossaApiClient sig m => m [CoreTypes.ReleaseGroup]
getReleaseGroups = sendSimple GetReleaseGroups
-getReleaseGroupReleases :: Has FossaApiClient sig m => Int -> m [ReleaseGroupRelease]
+getReleaseGroupReleases :: Has FossaApiClient sig m => Int -> m [CoreTypes.ReleaseGroupRelease]
getReleaseGroupReleases releaseGroupId = sendSimple $ GetReleaseGroupReleases releaseGroupId
+
+getProjectV2 :: Has FossaApiClient sig m => Text -> m CoreTypes.Project
+getProjectV2 locator = sendSimple $ GetProjectV2 locator
+
+updateProject :: Has FossaApiClient sig m => Text -> CoreTypes.UpdateProjectRequest -> m CoreTypes.Project
+updateProject locator req = sendSimple $ UpdateProject locator req
+
+updateRevision :: Has FossaApiClient sig m => Text -> CoreTypes.UpdateRevisionRequest -> m CoreTypes.Revision
+updateRevision revisionLocator req = sendSimple $ UpdateRevision revisionLocator req
+
+getOrgLabels :: Has FossaApiClient sig m => m CoreTypes.Labels
+getOrgLabels = sendSimple GetOrgLabels
diff --git a/src/Fossa/API/CoreTypes.hs b/src/Fossa/API/CoreTypes.hs
new file mode 100644
index 0000000000..45ccf1b869
--- /dev/null
+++ b/src/Fossa/API/CoreTypes.hs
@@ -0,0 +1,298 @@
+{-# LANGUAGE RecordWildCards #-}
+
+module Fossa.API.CoreTypes (
+ Project (..),
+ Policy (..),
+ Team (..),
+ PolicyType (..),
+ ReleaseGroup (..),
+ ReleaseGroupRelease (..),
+ ReleaseProject (..),
+ UpdateReleaseProjectRequest (..),
+ UpdateReleaseRequest (..),
+ CreateReleaseGroupRequest (..),
+ CreateReleaseGroupResponse (..),
+ UpdateProjectRequest (..),
+ AddTeamProjectsRequest (..),
+ AddTeamProjectsResponse (..),
+ TeamProjectAction (..),
+ UpdateRevisionRequest (..),
+ Label (..),
+ Labels (..),
+ Revision (..),
+) where
+
+import App.Types (ReleaseGroupReleaseRevision)
+import Data.Aeson (
+ FromJSON (parseJSON),
+ KeyValue ((.=)),
+ ToJSON (toJSON),
+ Value (Null),
+ object,
+ withObject,
+ withText,
+ (.:),
+ )
+import Data.Text (Text)
+
+--- | Data types of Core's main endpoints
+
+-- Policies
+data Policy = Policy
+ { policyId :: Int
+ , policyTitle :: Text
+ , policyType :: PolicyType
+ }
+ deriving (Eq, Ord, Show)
+
+instance FromJSON Policy where
+ parseJSON = withObject "Policy" $ \obj ->
+ Policy
+ <$> obj .: "id"
+ <*> obj .: "title"
+ <*> obj .: "type"
+
+instance FromJSON PolicyType where
+ parseJSON = withText "PolicyType" $ \case
+ "LICENSING" -> pure LICENSING
+ "SECURITY" -> pure SECURITY
+ "QUALITY" -> pure QUALITY
+ other -> pure $ PolicyUnknown other
+
+data PolicyType
+ = LICENSING
+ | SECURITY
+ | QUALITY
+ | PolicyUnknown Text
+ deriving (Eq, Ord, Show)
+
+-- Teams
+data Team = Team
+ { teamId :: Int
+ , teamName :: Text
+ }
+ deriving (Eq, Ord, Show)
+
+instance FromJSON Team where
+ parseJSON = withObject "Team" $ \obj ->
+ Team
+ <$> obj .: "id"
+ <*> obj .: "name"
+
+data TeamProjectAction = Add
+ deriving (Eq, Ord, Show)
+
+instance ToJSON TeamProjectAction where
+ toJSON Add = "add"
+
+data AddTeamProjectsRequest = AddTeamProjectsRequest
+ { projectLocators :: [Text]
+ , action :: TeamProjectAction
+ }
+ deriving (Eq, Ord, Show)
+
+instance ToJSON AddTeamProjectsRequest where
+ toJSON AddTeamProjectsRequest{..} =
+ object
+ [ "projects" .= projectLocators
+ , "action" .= action
+ ]
+
+newtype AddTeamProjectsResponse = AddTeamProjectsResponse {teamProjectLocators :: [Text]}
+ deriving (Eq, Ord, Show)
+
+instance FromJSON AddTeamProjectsResponse where
+ parseJSON = withObject "AddTeamProjectsResponse" $ \obj ->
+ AddTeamProjectsResponse
+ <$> obj .: "projects"
+
+-- ReleaseGroup
+data ReleaseGroup = ReleaseGroup
+ { releaseGroupId :: Int
+ , releaseGroupTitle :: Text
+ , releaseGroupReleases :: [ReleaseGroupRelease]
+ }
+ deriving (Eq, Ord, Show)
+
+instance FromJSON ReleaseGroup where
+ parseJSON = withObject "ReleaseGroup" $ \obj ->
+ ReleaseGroup
+ <$> obj .: "id"
+ <*> obj .: "title"
+ <*> obj .: "releases"
+
+data ReleaseGroupRelease = ReleaseGroupRelease
+ { releaseGroupReleaseId :: Int
+ , releaseGroupReleaseTitle :: Text
+ , releaseGroupReleaseProjects :: [ReleaseProject]
+ }
+ deriving (Eq, Ord, Show)
+
+instance FromJSON ReleaseGroupRelease where
+ parseJSON = withObject "ReleaseGroupRelease" $ \obj ->
+ ReleaseGroupRelease
+ <$> obj .: "id"
+ <*> obj .: "title"
+ <*> obj .: "projects"
+
+data ReleaseProject = ReleaseProject
+ { releaseProjectLocator :: Text
+ , releaseProjectRevisionId :: Text
+ , releaseProjectBranch :: Text
+ }
+ deriving (Eq, Ord, Show)
+
+instance FromJSON ReleaseProject where
+ parseJSON = withObject "ReleaseProject" $ \obj ->
+ ReleaseProject
+ <$> obj .: "projectId"
+ <*> obj .: "revisionId"
+ <*> obj .: "branch"
+
+data UpdateReleaseRequest = UpdateReleaseRequest
+ { updatedReleaseTitle :: Text
+ , updatedProjects :: [UpdateReleaseProjectRequest]
+ }
+ deriving (Eq, Ord, Show)
+
+instance ToJSON UpdateReleaseRequest where
+ toJSON UpdateReleaseRequest{..} =
+ object
+ [ "title" .= updatedReleaseTitle
+ , "projects" .= updatedProjects
+ ]
+
+data UpdateReleaseProjectRequest = UpdateReleaseProjectRequest
+ { updateReleaseProjectLocator :: Text
+ , updateReleaseProjectRevisionId :: Text
+ , updateReleaseProjectBranch :: Text
+ , -- Existence of this field signifies to core that the project exists and the release project just needs to be updated.
+ -- If this field is empty it means that we are adding a new project to the release.
+ targetReleaseGroupId :: Maybe Int
+ }
+ deriving (Eq, Ord, Show)
+
+instance ToJSON UpdateReleaseProjectRequest where
+ toJSON UpdateReleaseProjectRequest{..} =
+ object
+ [ "projectId" .= updateReleaseProjectLocator
+ , "revisionId" .= updateReleaseProjectRevisionId
+ , "branch" .= updateReleaseProjectBranch
+ , "projectGroupReleaseId" .= maybe Null toJSON targetReleaseGroupId
+ ]
+
+data CreateReleaseGroupRequest = CreateReleaseGroupRequest
+ { title :: Text
+ , release :: ReleaseGroupReleaseRevision
+ , maybeLicensePolicyId :: Maybe Int
+ , maybeSecurityPolicyId :: Maybe Int
+ , maybeQualityPolicyId :: Maybe Int
+ , maybeTeamIds :: Maybe [Int]
+ }
+ deriving (Eq, Ord, Show)
+
+instance ToJSON CreateReleaseGroupRequest where
+ toJSON CreateReleaseGroupRequest{..} =
+ object
+ [ "title" .= title
+ , "release" .= release
+ , "licensingPolicyId" .= maybe Null toJSON maybeLicensePolicyId
+ , "securityPolicyId" .= maybe Null toJSON maybeSecurityPolicyId
+ , "qualityPolicyId" .= maybe Null toJSON maybeQualityPolicyId
+ , "teams" .= maybe Null toJSON maybeTeamIds
+ ]
+
+newtype CreateReleaseGroupResponse = CreateReleaseGroupResponse {groupId :: Int}
+ deriving (Eq, Ord, Show)
+
+instance FromJSON CreateReleaseGroupResponse where
+ parseJSON = withObject "CreateReleaseGroupResponse" $ \obj ->
+ CreateReleaseGroupResponse
+ <$> obj .: "id"
+
+-- Project
+data Project = Project
+ { projectLocator :: Text
+ , projectTitle :: Text
+ , projectUrl :: Maybe Text
+ , projectIssueTrackerIds :: Maybe [Text]
+ , projectPolicyId :: Maybe Int
+ , projectLatestRevision :: Maybe Text
+ , projectDefaultBranch :: Maybe Text
+ }
+ deriving (Eq, Ord, Show)
+
+instance FromJSON Project where
+ parseJSON = withObject "ProjectResponse" $ \obj ->
+ Project
+ <$> obj .: "locator"
+ <*> obj .: "title"
+ <*> obj .: "url"
+ <*> obj .: "issueTrackerProjectIds"
+ <*> obj .: "policyId"
+ <*> obj .: "last_analyzed_revision"
+ <*> obj .: "default_branch"
+
+data UpdateProjectRequest = UpdateProjectRequest
+ { updateProjectTitle :: Maybe Text
+ , updateProjectUrl :: Maybe Text
+ , updateProjectIssueTrackerIds :: Maybe [Text]
+ , updateProjectLabelIds :: Maybe [Int]
+ , updateProjectPolicyId :: Maybe Int
+ , -- This field needs to be set, otherwise the default branch will be removed
+ updateProjectDefaultBranch :: Maybe Text
+ }
+ deriving (Eq, Ord, Show)
+
+instance ToJSON UpdateProjectRequest where
+ toJSON UpdateProjectRequest{..} =
+ object
+ [ "title" .= maybe Null toJSON updateProjectTitle
+ , "url" .= maybe Null toJSON updateProjectUrl
+ , "issueTrackerProjectIds" .= maybe Null toJSON updateProjectIssueTrackerIds
+ , "labels" .= maybe Null toJSON updateProjectLabelIds
+ , "policyId" .= maybe Null toJSON updateProjectPolicyId
+ , "default_branch" .= maybe Null toJSON updateProjectDefaultBranch
+ ]
+
+-- Revision
+data Revision = Revision
+ { revisionLocator :: Text
+ , revisionLink :: Maybe Text
+ }
+ deriving (Eq, Ord, Show)
+
+instance FromJSON Revision where
+ parseJSON = withObject "Revision" $ \obj ->
+ Revision
+ <$> obj .: "locator"
+ <*> obj .: "link"
+
+newtype UpdateRevisionRequest = UpdateRevisionRequest {newRevisionLink :: Text}
+ deriving (Eq, Ord, Show)
+
+instance ToJSON UpdateRevisionRequest where
+ toJSON UpdateRevisionRequest{..} =
+ object
+ ["link" .= newRevisionLink]
+
+-- Label
+data Label = Label
+ { labelId :: Int
+ , labelName :: Text
+ }
+ deriving (Eq, Ord, Show)
+
+instance FromJSON Label where
+ parseJSON = withObject "Label " $ \obj ->
+ Label
+ <$> obj .: "id"
+ <*> obj .: "label"
+
+newtype Labels = Labels {labels :: [Label]}
+ deriving (Eq, Ord, Show)
+
+instance FromJSON Labels where
+ parseJSON = withObject "Labels" $ \obj ->
+ Labels
+ <$> obj .: "labels"
diff --git a/src/Fossa/API/Types.hs b/src/Fossa/API/Types.hs
index 8daeb4969c..34153e4223 100644
--- a/src/Fossa/API/Types.hs
+++ b/src/Fossa/API/Types.hs
@@ -38,23 +38,13 @@ module Fossa.API.Types (
CustomBuildUploadPermissions (..),
ProjectPermissionStatus (..),
ReleaseGroupPermissionStatus (..),
- Policy (..),
- Team (..),
- PolicyType (..),
- ReleaseGroup (..),
- ReleaseGroupRelease (..),
- ReleaseProject (..),
- UpdateReleaseProjectRequest (..),
- UpdateReleaseRequest (..),
- CreateReleaseGroupRequest (..),
- CreateReleaseGroupResponse (..),
useApiOpts,
defaultApiPollDelay,
blankOrganization,
) where
import App.Fossa.Lernie.Types (GrepEntry)
-import App.Types (FullFileUploads (..), ReleaseGroupReleaseRevision, fullFileUploadsToCliLicenseScanType)
+import App.Types (FullFileUploads (..), fullFileUploadsToCliLicenseScanType)
import Control.Applicative ((<|>))
import Control.Effect.Diagnostics (Diagnostics, Has, fatalText)
import Control.Timeout (Duration (Seconds))
@@ -62,7 +52,7 @@ import Data.Aeson (
FromJSON (parseJSON),
KeyValue ((.=)),
ToJSON (toJSON),
- Value (Null, String),
+ Value (String),
object,
withObject,
withText,
@@ -101,6 +91,8 @@ import Text.URI.QQ (uri)
import Types (ArchiveUploadType (..))
import Unsafe.Coerce qualified as Unsafe
+--- | Data types of CLI owned endpoints in Core
+
newtype ApiKey = ApiKey {unApiKey :: Text}
deriving (Eq, Ord)
@@ -954,149 +946,3 @@ instance FromJSON AnalyzedPathDependency where
<$> obj .: "path"
<*> obj .: "id"
<*> obj .: "version"
-
---- Policies
-data Policy = Policy
- { policyId :: Int
- , policyTitle :: Text
- , policyType :: PolicyType
- }
- deriving (Eq, Ord, Show)
-
-instance FromJSON Policy where
- parseJSON = withObject "Policy" $ \obj ->
- Policy
- <$> obj .: "id"
- <*> obj .: "title"
- <*> obj .: "type"
-
-instance FromJSON PolicyType where
- parseJSON = withText "PolicyType" $ \case
- "LICENSING" -> pure LICENSING
- "SECURITY" -> pure SECURITY
- "QUALITY" -> pure QUALITY
- other -> pure $ PolicyUnknown other
-
-data PolicyType
- = LICENSING
- | SECURITY
- | QUALITY
- | PolicyUnknown Text
- deriving (Eq, Ord, Show)
-
--- Teams
-data Team = Team
- { teamId :: Int
- , teamName :: Text
- }
- deriving (Eq, Ord, Show)
-
-instance FromJSON Team where
- parseJSON = withObject "Team" $ \obj ->
- Team
- <$> obj .: "id"
- <*> obj .: "name"
-
--- ReleaseGroup
-data ReleaseGroup = ReleaseGroup
- { releaseGroupId :: Int
- , releaseGroupTitle :: Text
- , releaseGroupReleases :: [ReleaseGroupRelease]
- }
- deriving (Eq, Ord, Show)
-
-instance FromJSON ReleaseGroup where
- parseJSON = withObject "ReleaseGroup" $ \obj ->
- ReleaseGroup
- <$> obj .: "id"
- <*> obj .: "title"
- <*> obj .: "releases"
-
-data ReleaseGroupRelease = ReleaseGroupRelease
- { releaseGroupReleaseId :: Int
- , releaseGroupReleaseTitle :: Text
- , releaseGroupReleaseProjects :: [ReleaseProject]
- }
- deriving (Eq, Ord, Show)
-
-instance FromJSON ReleaseGroupRelease where
- parseJSON = withObject "ReleaseGroupRelease" $ \obj ->
- ReleaseGroupRelease
- <$> obj .: "id"
- <*> obj .: "title"
- <*> obj .: "projects"
-
-data ReleaseProject = ReleaseProject
- { releaseProjectLocator :: Text
- , releaseProjectRevisionId :: Text
- , releaseProjectBranch :: Text
- }
- deriving (Eq, Ord, Show)
-
-instance FromJSON ReleaseProject where
- parseJSON = withObject "ReleaseProject" $ \obj ->
- ReleaseProject
- <$> obj .: "projectId"
- <*> obj .: "revisionId"
- <*> obj .: "branch"
-
-data UpdateReleaseRequest = UpdateReleaseRequest
- { updatedReleaseTitle :: Text
- , updatedProjects :: [UpdateReleaseProjectRequest]
- }
- deriving (Eq, Ord, Show)
-
-instance ToJSON UpdateReleaseRequest where
- toJSON UpdateReleaseRequest{..} =
- object
- [ "title" .= updatedReleaseTitle
- , "projects" .= updatedProjects
- ]
-
-data UpdateReleaseProjectRequest = UpdateReleaseProjectRequest
- { updateReleaseProjectLocator :: Text
- , updateReleaseProjectRevisionId :: Text
- , updateReleaseProjectBranch :: Text
- , -- Existence of this field signifies to core that the project exists and the release project just needs to be updated.
- -- If this field is empty it means that we are adding a new project to the release.
- targetReleaseGroupId :: Maybe Int
- }
- deriving (Eq, Ord, Show)
-
-instance ToJSON UpdateReleaseProjectRequest where
- toJSON UpdateReleaseProjectRequest{..} =
- object
- [ "projectId" .= updateReleaseProjectLocator
- , "revisionId" .= updateReleaseProjectRevisionId
- , "branch" .= updateReleaseProjectBranch
- , "projectGroupReleaseId" .= maybe Null toJSON targetReleaseGroupId
- ]
-
-data CreateReleaseGroupRequest = CreateReleaseGroupRequest
- { title :: Text
- , release :: ReleaseGroupReleaseRevision
- , maybeLicensePolicyId :: Maybe Int
- , maybeSecurityPolicyId :: Maybe Int
- , maybeQualityPolicyId :: Maybe Int
- , maybeTeamIds :: Maybe [Int]
- }
- deriving (Eq, Ord, Show)
-
-instance ToJSON CreateReleaseGroupRequest where
- toJSON CreateReleaseGroupRequest{..} =
- object
- [ "title" .= title
- , "release" .= release
- , "licensingPolicyId" .= maybe Null toJSON maybeLicensePolicyId
- , "securityPolicyId" .= maybe Null toJSON maybeSecurityPolicyId
- , "qualityPolicyId" .= maybe Null toJSON maybeQualityPolicyId
- , "teams" .= maybe Null toJSON maybeTeamIds
- ]
-
-newtype CreateReleaseGroupResponse = CreateReleaseGroupResponse {groupId :: Int}
- deriving (Eq, Ord, Show)
-
-instance FromJSON CreateReleaseGroupResponse where
- parseJSON = withObject "CreateReleaseGroupResponse" $ \obj ->
- CreateReleaseGroupResponse
- <$> obj .: "id"
diff --git a/test/App/Fossa/ApiUtilsSpec.hs b/test/App/Fossa/ApiUtilsSpec.hs
new file mode 100644
index 0000000000..814ab325c2
--- /dev/null
+++ b/test/App/Fossa/ApiUtilsSpec.hs
@@ -0,0 +1,84 @@
+module App.Fossa.ApiUtilsSpec (
+ spec,
+) where
+
+import App.Fossa.ApiUtils (retrieveLabelIds, retrievePolicyId, retrieveTeamIds, retrieveTeamIdsWithMaybe)
+import Fossa.API.CoreTypes (Label (..), Labels (..), PolicyType (..), Team (..))
+import Test.Effect (expectFatal', it', shouldBe')
+import Test.Fixtures qualified as Fixtures
+import Test.Hspec (Spec, describe)
+
+spec :: Spec
+spec = do
+ retrievePolicyIdSpec
+ retrieveTeamIdsWithMaybeSpec
+ retrieveTeamIdsSpec
+ retrieveLabelIdsSpec
+
+retrievePolicyIdSpec :: Spec
+retrievePolicyIdSpec = do
+ describe "retrievePolicyId" $ do
+ it' "should fail when the policy title does not exist" $ do
+ let policies = [Fixtures.policy]
+ expectFatal' $ retrievePolicyId (Just "non-existent-policy-name") LICENSING policies
+
+ it' "should fail when the policy type does not match" $ do
+ let policies = [Fixtures.policy]
+ expectFatal' $ retrievePolicyId (Just "example-policy") SECURITY policies
+
+ it' "should fail when there are multiple policies with the same name" $ do
+ let policies = [Fixtures.policy, Fixtures.policy]
+ expectFatal' $ retrievePolicyId (Just "example-policy") SECURITY policies
+
+ it' "should return Nothing" $ do
+ let policies = [Fixtures.policy]
+ res <- retrievePolicyId Nothing LICENSING policies
+ res `shouldBe'` Nothing
+
+ it' "should successfully retrieve policy id" $ do
+ let policies = [Fixtures.policy]
+ res <- retrievePolicyId (Just "example-policy") LICENSING policies
+ res `shouldBe'` Just 7
+
+retrieveTeamIdsWithMaybeSpec :: Spec
+retrieveTeamIdsWithMaybeSpec = do
+ describe "retrieveTeamIdsWithMaybe" $ do
+ it' "should return Nothing if the team names is Nothing" $ do
+ let teams = [Fixtures.team, Fixtures.team{teamId = 11, teamName = "example-team-2"}]
+ res <- retrieveTeamIdsWithMaybe Nothing teams
+ res `shouldBe'` Nothing
+
+ it' "should successfully retrieve all team ids" $ do
+ let teams = [Fixtures.team, Fixtures.team{teamId = 11, teamName = "example-team-2"}]
+ res <- retrieveTeamIdsWithMaybe (Just ["example-team", "example-team-2"]) teams
+ res `shouldBe'` Just [10, 11]
+
+retrieveTeamIdsSpec :: Spec
+retrieveTeamIdsSpec = do
+ describe "retrieveTeamIds" $ do
+ it' "should fail if not all team names exist in org" $ do
+ let teams = [Fixtures.team, Fixtures.team{teamId = 11, teamName = "example-team-2"}]
+ expectFatal' $ retrieveTeamIds (["example-team-2", "non-existent-team"]) teams
+
+ it' "should successfully retrieve all team ids" $ do
+ let teams = [Fixtures.team, Fixtures.team{teamId = 11, teamName = "example-team-2"}]
+ res <- retrieveTeamIds ["example-team", "example-team-2"] teams
+ res `shouldBe'` [10, 11]
+
+retrieveLabelIdsSpec :: Spec
+retrieveLabelIdsSpec = do
+ describe "retrieveLabelIds" $ do
+ let label1 = Label 1 "example-label"
+ let label2 = Label 2 "example-label-2"
+ let labels = Labels [label1, label2]
+
+ it' "should return empty list and label warnings when no label names exists Labels" $ do
+ res <- retrieveLabelIds ["non-existent-label", "non-existent-label-2"] labels
+ let warning1 = "Label `non-existent-label` does not exist\nNavigate to `Organization Settings` in the FOSSA web UI to create new labels: https://app.fossa.com/account/settings/organization"
+ warning2 = "Label `non-existent-label-2` does not exist\nNavigate to `Organization Settings` in the FOSSA web UI to create new labels: https://app.fossa.com/account/settings/organization"
+ expectedWarnings = Just [warning1, warning2]
+ res `shouldBe'` ([], expectedWarnings)
+
+ it' "should return all labels" $ do
+ res <- retrieveLabelIds ["example-label", "example-label-2"] labels
+ res `shouldBe'` ([2, 1], Nothing)
diff --git a/test/App/Fossa/Configuration/ConfigurationSpec.hs b/test/App/Fossa/Configuration/ConfigurationSpec.hs
index 0a0d9ae1fc..ecc46c6ee7 100644
--- a/test/App/Fossa/Configuration/ConfigurationSpec.hs
+++ b/test/App/Fossa/Configuration/ConfigurationSpec.hs
@@ -81,10 +81,12 @@ expectedReleaseGroupProject =
expectedConfigProject :: ConfigProject
expectedConfigProject =
ConfigProject
- { configProjID = Just "github.com/fossa-cli"
+ { configProjLocator = Just "custom+1/github.com/fossa-cli"
+ , configProjID = Just "github.com/fossa-cli"
, configName = Just "fossa-cli"
, configLink = Just "fossa.com"
, configTeam = Just "fossa-team"
+ , configTeams = Just ["fossa-team"]
, configJiraKey = Just "key"
, configUrl = Just "fossa.com"
, configPolicy = Just (PolicyName "license-policy")
diff --git a/test/App/Fossa/Configuration/testdata/valid-default-yaml/.fossa.yaml b/test/App/Fossa/Configuration/testdata/valid-default-yaml/.fossa.yaml
index 4a98bf8f48..3f03554518 100644
--- a/test/App/Fossa/Configuration/testdata/valid-default-yaml/.fossa.yaml
+++ b/test/App/Fossa/Configuration/testdata/valid-default-yaml/.fossa.yaml
@@ -4,9 +4,12 @@ server: https://app.fossa.com
apiKey: "123"
project:
+ locator: custom+1/github.com/fossa-cli
id: github.com/fossa-cli
name: fossa-cli
team: fossa-team
+ teams:
+ - fossa-team
policy: license-policy
link: fossa.com
url: fossa.com
diff --git a/test/App/Fossa/Configuration/testdata/valid-default/.fossa.yml b/test/App/Fossa/Configuration/testdata/valid-default/.fossa.yml
index 36c18f0bcd..e28018be14 100644
--- a/test/App/Fossa/Configuration/testdata/valid-default/.fossa.yml
+++ b/test/App/Fossa/Configuration/testdata/valid-default/.fossa.yml
@@ -4,9 +4,12 @@ server: https://app.fossa.com
apiKey: "123"
project:
+ locator: custom+1/github.com/fossa-cli
id: github.com/fossa-cli
name: fossa-cli
team: fossa-team
+ teams:
+ - fossa-team
policy: license-policy
link: fossa.com
url: fossa.com
diff --git a/test/App/Fossa/Project/EditSpec.hs b/test/App/Fossa/Project/EditSpec.hs
new file mode 100644
index 0000000000..5717b455c0
--- /dev/null
+++ b/test/App/Fossa/Project/EditSpec.hs
@@ -0,0 +1,215 @@
+module App.Fossa.Project.EditSpec (
+ spec,
+ apiOpts',
+) where
+
+import App.Fossa.Config.Project.Edit (EditConfig (..), ProjectIdentifier (..))
+import App.Fossa.Project.Edit (editMain)
+import App.Types (Policy (..))
+import Control.Algebra (Has)
+import Control.Carrier.Debug (ignoreDebug)
+import Control.Effect.FossaApiClient (FossaApiClientF (..))
+import Data.Text (Text)
+import Fossa.API.CoreTypes qualified as Types
+import Fossa.API.Types (ApiKey (..), ApiOpts (..), OrgId (..), Organization (organizationId), defaultApiPollDelay)
+import Test.Effect (expectFatal', it', shouldBe')
+import Test.Fixtures qualified as Fixtures
+import Test.Hspec (Spec, describe)
+import Test.MockApi (MockApi, alwaysReturns, returnsOnce)
+
+apiOpts' :: ApiOpts
+apiOpts' =
+ ApiOpts
+ { apiOptsUri = Nothing
+ , apiOptsApiKey = ApiKey "123"
+ , apiOptsPollDelay = defaultApiPollDelay
+ }
+
+locator :: Text
+locator = "custom+123/example-locator"
+
+projId :: Text
+projId = "example-locator"
+
+revisionLocator :: Text
+revisionLocator = locator <> "$789"
+
+title :: Text
+title = "example-title"
+
+url :: Text
+url = "example-url"
+
+jiraKey :: Text
+jiraKey = "example-jira-key"
+
+link :: Text
+link = "example-link"
+
+policyName :: Text
+policyName = "example-policy"
+
+teamName :: Text
+teamName = "example-team"
+
+teamName2 :: Text
+teamName2 = "example-team-2"
+
+labelName :: Text
+labelName = "example-label"
+
+labelName2 :: Text
+labelName2 = "example-label-2"
+
+editConfig :: EditConfig
+editConfig =
+ EditConfig
+ { apiOpts = apiOpts'
+ , projectIdentifier = ProjectLocator locator
+ , projectTitle = Just title
+ , projectUrl = Just url
+ , projectJiraKey = Just jiraKey
+ , projectLink = Just link
+ , teams = Just [teamName, teamName2]
+ , projectPolicy = Just $ PolicyName policyName
+ , projectLabels = Just [labelName2, labelName]
+ }
+
+emptyEditConfig :: EditConfig
+emptyEditConfig =
+ EditConfig
+ { apiOpts = apiOpts'
+ , projectIdentifier = ProjectLocator locator
+ , projectTitle = Nothing
+ , projectUrl = Nothing
+ , projectJiraKey = Nothing
+ , projectLink = Nothing
+ , teams = Nothing
+ , projectPolicy = Nothing
+ , projectLabels = Nothing
+ }
+
+updateProjectReq :: Types.UpdateProjectRequest
+updateProjectReq =
+ Types.UpdateProjectRequest
+ { Types.updateProjectTitle = Just title
+ , Types.updateProjectUrl = Just url
+ , Types.updateProjectIssueTrackerIds = Just [jiraKey]
+ , Types.updateProjectLabelIds = Just [20, 21]
+ , Types.updateProjectPolicyId = Just 7
+ , Types.updateProjectDefaultBranch = Just "main"
+ }
+
+updateRevisionReq :: Types.UpdateRevisionRequest
+updateRevisionReq = Types.UpdateRevisionRequest{Types.newRevisionLink = link}
+
+addTeamProjectsReq :: Types.AddTeamProjectsRequest
+addTeamProjectsReq =
+ Types.AddTeamProjectsRequest
+ { Types.projectLocators = [locator]
+ , Types.action = Types.Add
+ }
+
+addTeamProjectsRes :: Types.AddTeamProjectsResponse
+addTeamProjectsRes = Types.AddTeamProjectsResponse{Types.teamProjectLocators = [locator]}
+
+label1 :: Types.Label
+label1 =
+ Types.Label
+ { Types.labelId = 20
+ , Types.labelName = labelName
+ }
+
+label2 :: Types.Label
+label2 =
+ Types.Label
+ { Types.labelId = 21
+ , Types.labelName = labelName2
+ }
+
+labels :: Types.Labels
+labels = Types.Labels{Types.labels = [label1, label2]}
+
+initialProject :: Types.Project
+initialProject =
+ Types.Project
+ { Types.projectLocator = locator
+ , Types.projectTitle = "initial-title"
+ , Types.projectUrl = Nothing
+ , Types.projectIssueTrackerIds = Just [jiraKey]
+ , Types.projectPolicyId = Nothing
+ , Types.projectLatestRevision = Just revisionLocator
+ , Types.projectDefaultBranch = Just "main"
+ }
+
+updatedProject :: Types.Project
+updatedProject =
+ Types.Project
+ { Types.projectLocator = locator
+ , Types.projectTitle = title
+ , Types.projectUrl = Just url
+ , Types.projectIssueTrackerIds = Nothing
+ , Types.projectPolicyId = Just 7
+ , Types.projectLatestRevision = Just revisionLocator
+ , Types.projectDefaultBranch = Just "main"
+ }
+
+revision :: Types.Revision
+revision =
+ Types.Revision
+ { Types.revisionLocator = revisionLocator
+ , Types.revisionLink = Just link
+ }
+
+expectUpdateProjectSuccess :: Has MockApi sig m => m ()
+expectUpdateProjectSuccess = UpdateProject locator updateProjectReq `alwaysReturns` updatedProject
+
+expectGetOrgSuccess :: Has MockApi sig m => m ()
+expectGetOrgSuccess = GetOrganization `alwaysReturns` Fixtures.organization{organizationId = OrgId 123}
+
+expectGetProjectSuccess :: Has MockApi sig m => m ()
+expectGetProjectSuccess = GetProjectV2 locator `alwaysReturns` initialProject
+
+expectUpdateRevisionSuccess :: Has MockApi sig m => m ()
+expectUpdateRevisionSuccess = UpdateRevision revisionLocator updateRevisionReq `alwaysReturns` revision
+
+expectGetTeamsSuccess :: Has MockApi sig m => m ()
+expectGetTeamsSuccess = GetTeams `alwaysReturns` [Fixtures.team, Fixtures.team{Types.teamId = 11, Types.teamName = teamName2}]
+
+expectGetPoliciesSuccess :: Has MockApi sig m => m ()
+expectGetPoliciesSuccess = GetPolicies `alwaysReturns` [Fixtures.policy]
+
+expectGetOrgLabelsSuccess :: Has MockApi sig m => m ()
+expectGetOrgLabelsSuccess = GetOrgLabels `alwaysReturns` labels
+
+spec :: Spec
+spec = do
+ describe "Edit Project" $ do
+ it' "should fail when a project does not have a latest revision" $ do
+ GetProjectV2 locator `returnsOnce` initialProject{Types.projectLatestRevision = Nothing}
+ expectFatal' $ ignoreDebug $ editMain emptyEditConfig{projectLink = Just link}
+
+ it' "should successfully execute with ProjectIdentifier ProjectLocator" $ do
+ expectGetProjectSuccess
+ expectGetPoliciesSuccess
+ expectGetOrgLabelsSuccess
+ expectUpdateProjectSuccess
+ expectGetTeamsSuccess
+ AddTeamProjects 10 addTeamProjectsReq `returnsOnce` addTeamProjectsRes
+ AddTeamProjects 11 addTeamProjectsReq `returnsOnce` addTeamProjectsRes
+ expectUpdateRevisionSuccess
+ res <- ignoreDebug $ editMain editConfig
+ res `shouldBe'` ()
+
+ it' "should successfully execute with ProjectIdentifier ProjectId" $ do
+ expectGetOrgSuccess
+ expectGetProjectSuccess
+ expectGetPoliciesSuccess
+ expectGetOrgLabelsSuccess
+ expectUpdateProjectSuccess
+ expectGetTeamsSuccess
+ AddTeamProjects 10 addTeamProjectsReq `returnsOnce` addTeamProjectsRes
+ AddTeamProjects 11 addTeamProjectsReq `returnsOnce` addTeamProjectsRes
+ expectUpdateRevisionSuccess
+ res <- ignoreDebug $ editMain editConfig{projectIdentifier = ProjectId projId}
+ res `shouldBe'` ()
diff --git a/test/App/Fossa/ReleaseGroup/AddProjectsSpec.hs b/test/App/Fossa/ReleaseGroup/AddProjectsSpec.hs
index fd12004f3e..5e75c757d1 100644
--- a/test/App/Fossa/ReleaseGroup/AddProjectsSpec.hs
+++ b/test/App/Fossa/ReleaseGroup/AddProjectsSpec.hs
@@ -9,8 +9,8 @@ import App.Fossa.ReleaseGroup.CreateSpec (apiOpts')
import Control.Algebra (Has)
import Control.Carrier.Debug (ignoreDebug)
import Control.Effect.FossaApiClient (FossaApiClientF (..))
-import Fossa.API.Types (UpdateReleaseProjectRequest (..), UpdateReleaseRequest (..))
-import Fossa.API.Types qualified as Types
+import Fossa.API.CoreTypes (UpdateReleaseProjectRequest (..), UpdateReleaseRequest (..))
+import Fossa.API.CoreTypes qualified as Types
import Test.Effect (expectFatal', it', shouldBe')
import Test.Fixtures qualified as Fixtures
import Test.Hspec (Spec, describe, it, shouldBe)
diff --git a/test/App/Fossa/ReleaseGroup/CommonSpec.hs b/test/App/Fossa/ReleaseGroup/CommonSpec.hs
index a100ff8d78..5d15d4ba52 100644
--- a/test/App/Fossa/ReleaseGroup/CommonSpec.hs
+++ b/test/App/Fossa/ReleaseGroup/CommonSpec.hs
@@ -3,7 +3,7 @@ module App.Fossa.ReleaseGroup.CommonSpec (
) where
import App.Fossa.ReleaseGroup.Common (retrieveReleaseGroupId, retrieveReleaseGroupRelease)
-import Fossa.API.Types (ReleaseGroupRelease (..))
+import Fossa.API.CoreTypes (ReleaseGroupRelease (..))
import Test.Effect (expectFatal', it', shouldBe')
import Test.Fixtures qualified as Fixtures
import Test.Hspec (Spec, describe)
diff --git a/test/App/Fossa/ReleaseGroup/CreateReleaseSpec.hs b/test/App/Fossa/ReleaseGroup/CreateReleaseSpec.hs
index f077956af1..6cca26a005 100644
--- a/test/App/Fossa/ReleaseGroup/CreateReleaseSpec.hs
+++ b/test/App/Fossa/ReleaseGroup/CreateReleaseSpec.hs
@@ -9,7 +9,7 @@ import App.Fossa.ReleaseGroup.CreateSpec (apiOpts')
import Control.Algebra (Has)
import Control.Carrier.Debug (ignoreDebug)
import Control.Effect.FossaApiClient (FossaApiClientF (..))
-import Fossa.API.Types qualified as Types
+import Fossa.API.CoreTypes qualified as Types
import Test.Effect (expectFatal', it', shouldBe')
import Test.Fixtures qualified as Fixtures
import Test.Hspec (Spec, describe)
diff --git a/test/App/Fossa/ReleaseGroup/CreateSpec.hs b/test/App/Fossa/ReleaseGroup/CreateSpec.hs
index 100df16861..504f07b368 100644
--- a/test/App/Fossa/ReleaseGroup/CreateSpec.hs
+++ b/test/App/Fossa/ReleaseGroup/CreateSpec.hs
@@ -5,11 +5,12 @@ module App.Fossa.ReleaseGroup.CreateSpec (
import App.Fossa.Config.ReleaseGroup.Create (CreateConfig (..))
import App.Fossa.Config.ReleaseGroup.CreateSpec (expectedReleaseGroupReleaseRevisionFromConfig, expectedReleaseGroupRevisionFromConfig)
-import App.Fossa.ReleaseGroup.Create (createMain, retrievePolicyId, retrieveTeamIds)
+import App.Fossa.ReleaseGroup.Create (createMain)
import Control.Algebra (Has)
import Control.Carrier.Debug (ignoreDebug)
import Control.Effect.FossaApiClient (FossaApiClientF (..))
-import Fossa.API.Types (ApiKey (..), ApiOpts (..), CreateReleaseGroupRequest (..), Policy (..), PolicyType (LICENSING, QUALITY, SECURITY), Team (..), defaultApiPollDelay)
+import Fossa.API.CoreTypes (CreateReleaseGroupRequest (..), Policy (..), PolicyType (QUALITY, SECURITY), Team (..))
+import Fossa.API.Types (ApiKey (..), ApiOpts (..), defaultApiPollDelay)
import Test.Effect (expectFatal', it', shouldBe')
import Test.Fixtures qualified as Fixtures
import Test.Hspec (Spec, describe)
@@ -47,9 +48,6 @@ expectCreateReleaseGroupSuccess = CreateReleaseGroup createReleaseGroupReq `alwa
spec :: Spec
spec = do
describe "Create Release Group" $ do
- retrievePolicyIdSpec
- retrieveTeanIdsSpec
-
it' "should fail when a release group with the same name already exists" $ do
GetReleaseGroups `returnsOnce` [Fixtures.releaseGroup]
expectFatal' $ ignoreDebug $ createMain createConfig
@@ -61,45 +59,3 @@ spec = do
expectCreateReleaseGroupSuccess
res <- ignoreDebug $ createMain createConfig
res `shouldBe'` ()
-
-retrievePolicyIdSpec :: Spec
-retrievePolicyIdSpec = do
- describe "retrievePolicyId" $ do
- it' "should fail when the policy title does not exist" $ do
- let policies = [Fixtures.policy]
- expectFatal' $ retrievePolicyId (Just "non-existent-policy-name") LICENSING policies
-
- it' "should fail when the policy type does not match" $ do
- let policies = [Fixtures.policy]
- expectFatal' $ retrievePolicyId (Just "example-policy") SECURITY policies
-
- it' "should fail when there are multiple policies with the same name" $ do
- let policies = [Fixtures.policy, Fixtures.policy]
- expectFatal' $ retrievePolicyId (Just "example-policy") SECURITY policies
-
- it' "should return Nothing" $ do
- let policies = [Fixtures.policy]
- res <- retrievePolicyId Nothing LICENSING policies
- res `shouldBe'` Nothing
-
- it' "should successfully retrieve policy id" $ do
- let policies = [Fixtures.policy]
- res <- retrievePolicyId (Just "example-policy") LICENSING policies
- res `shouldBe'` Just 7
-
-retrieveTeanIdsSpec :: Spec
-retrieveTeanIdsSpec = do
- describe "retrieveTeamIds" $ do
- it' "should fail if not all team names exist in org" $ do
- let teams = [Fixtures.team, Fixtures.team{teamId = 11, teamName = "example-team-2"}]
- expectFatal' $ retrieveTeamIds (Just ["example-team-2", "non-existent-team"]) teams
-
- it' "should successfully retrieve all team ids" $ do
- let teams = [Fixtures.team, Fixtures.team{teamId = 11, teamName = "example-team-2"}]
- res <- retrieveTeamIds Nothing teams
- res `shouldBe'` Nothing
-
- it' "should successfully retrieve all team ids" $ do
- let teams = [Fixtures.team, Fixtures.team{teamId = 11, teamName = "example-team-2"}]
- res <- retrieveTeamIds (Just ["example-team", "example-team-2"]) teams
- res `shouldBe'` Just [10, 11]
diff --git a/test/App/Fossa/ReleaseGroup/DeleteReleaseSpec.hs b/test/App/Fossa/ReleaseGroup/DeleteReleaseSpec.hs
index 78e60f7d1c..400ba1f485 100644
--- a/test/App/Fossa/ReleaseGroup/DeleteReleaseSpec.hs
+++ b/test/App/Fossa/ReleaseGroup/DeleteReleaseSpec.hs
@@ -8,7 +8,7 @@ import App.Fossa.ReleaseGroup.DeleteRelease (deleteReleaseMain)
import Control.Algebra (Has)
import Control.Carrier.Debug (ignoreDebug)
import Control.Effect.FossaApiClient (FossaApiClientF (..))
-import Fossa.API.Types qualified as Types
+import Fossa.API.CoreTypes qualified as Types
import Test.Effect (expectFatal', it', shouldBe')
import Test.Fixtures qualified as Fixtures
import Test.Hspec (Spec, describe)
diff --git a/test/App/Fossa/ReleaseGroup/DeleteSpec.hs b/test/App/Fossa/ReleaseGroup/DeleteSpec.hs
index c1c37db0a8..165999cc2d 100644
--- a/test/App/Fossa/ReleaseGroup/DeleteSpec.hs
+++ b/test/App/Fossa/ReleaseGroup/DeleteSpec.hs
@@ -6,7 +6,7 @@ import App.Fossa.ReleaseGroup.Delete (deleteMain)
import Control.Algebra (Has)
import Control.Carrier.Debug (ignoreDebug)
import Control.Effect.FossaApiClient (FossaApiClientF (..))
-import Fossa.API.Types (ReleaseGroup (releaseGroupTitle))
+import Fossa.API.CoreTypes qualified as Types
import Test.Effect (expectFatal', it', shouldBe')
import Test.Fixtures qualified as Fixtures
import Test.Hspec (Spec, describe)
@@ -25,7 +25,7 @@ spec :: Spec
spec = do
describe "Delete Release Group" $ do
it' "should fail when the release group to delete does not exist when deleting a release group" $ do
- GetReleaseGroups `returnsOnce` [Fixtures.releaseGroup{Fossa.API.Types.releaseGroupTitle = "example-title-2"}]
+ GetReleaseGroups `returnsOnce` [Fixtures.releaseGroup{Types.releaseGroupTitle = "example-title-2"}]
expectFatal' $ ignoreDebug $ deleteMain deleteConfig
it' "should fail to delete release group" $ do
diff --git a/test/Test/Fixtures.hs b/test/Test/Fixtures.hs
index c7b8f7a8a3..e58c33fafa 100644
--- a/test/Test/Fixtures.hs
+++ b/test/Test/Fixtures.hs
@@ -82,7 +82,8 @@ import Data.Text.Encoding qualified as TL
import Data.Text.Extra (showT)
import Discovery.Filters (AllFilters, MavenScopeFilters (MavenScopeIncludeFilters))
import Effect.Logger (Severity (..))
-import Fossa.API.Types (Archive (..), PolicyType (..))
+import Fossa.API.CoreTypes qualified as CoreAPI
+import Fossa.API.Types (Archive (..))
import Fossa.API.Types qualified as API
import Path (Abs, Dir, Path, mkAbsDir, mkRelDir, parseAbsDir, (>))
import Srclib.Types (LicenseScanType (..), LicenseSourceUnit (..), Locator (..), SourceUnit (..), SourceUnitBuild (..), SourceUnitDependency (..), emptyLicenseUnit)
@@ -135,46 +136,46 @@ invalidCreateTeamProjectsForReleaseGroupPermission = API.CustomBuildUploadPermis
validCustomUploadPermissions :: API.CustomBuildUploadPermissions
validCustomUploadPermissions = API.CustomBuildUploadPermissions API.ValidProjectPermission $ Just API.ValidReleaseGroupPermission
-createReleaseGroupResponse :: API.CreateReleaseGroupResponse
-createReleaseGroupResponse = API.CreateReleaseGroupResponse 1
+createReleaseGroupResponse :: CoreAPI.CreateReleaseGroupResponse
+createReleaseGroupResponse = CoreAPI.CreateReleaseGroupResponse 1
-releaseGroup :: API.ReleaseGroup
+releaseGroup :: CoreAPI.ReleaseGroup
releaseGroup =
- API.ReleaseGroup
- { API.releaseGroupId = 1
- , API.releaseGroupTitle = "example-title"
- , API.releaseGroupReleases = [release]
+ CoreAPI.ReleaseGroup
+ { CoreAPI.releaseGroupId = 1
+ , CoreAPI.releaseGroupTitle = "example-title"
+ , CoreAPI.releaseGroupReleases = [release]
}
-release :: API.ReleaseGroupRelease
+release :: CoreAPI.ReleaseGroupRelease
release =
- API.ReleaseGroupRelease
- { API.releaseGroupReleaseId = 2
- , API.releaseGroupReleaseTitle = "example-release-title"
- , API.releaseGroupReleaseProjects = [releaseProject]
+ CoreAPI.ReleaseGroupRelease
+ { CoreAPI.releaseGroupReleaseId = 2
+ , CoreAPI.releaseGroupReleaseTitle = "example-release-title"
+ , CoreAPI.releaseGroupReleaseProjects = [releaseProject]
}
-releaseProject :: API.ReleaseProject
+releaseProject :: CoreAPI.ReleaseProject
releaseProject =
- API.ReleaseProject
- { API.releaseProjectLocator = "custom+1/example"
- , API.releaseProjectRevisionId = "custom+1/example$123"
- , API.releaseProjectBranch = "main"
+ CoreAPI.ReleaseProject
+ { CoreAPI.releaseProjectLocator = "custom+1/example"
+ , CoreAPI.releaseProjectRevisionId = "custom+1/example$123"
+ , CoreAPI.releaseProjectBranch = "main"
}
-policy :: API.Policy
+policy :: CoreAPI.Policy
policy =
- API.Policy
- { API.policyId = 7
- , API.policyTitle = "example-policy"
- , API.policyType = LICENSING
+ CoreAPI.Policy
+ { CoreAPI.policyId = 7
+ , CoreAPI.policyTitle = "example-policy"
+ , CoreAPI.policyType = CoreAPI.LICENSING
}
-team :: API.Team
+team :: CoreAPI.Team
team =
- API.Team
- { API.teamId = 10
- , API.teamName = "example-team"
+ CoreAPI.Team
+ { CoreAPI.teamId = 10
+ , CoreAPI.teamName = "example-team"
}
project :: API.Project
diff --git a/test/Test/MockApi.hs b/test/Test/MockApi.hs
index 77d3033027..f6ce7209ce 100644
--- a/test/Test/MockApi.hs
+++ b/test/Test/MockApi.hs
@@ -245,6 +245,11 @@ matchExpectation a@(GetReleaseGroups{}) (ApiExpectation _ requestExpectation b@(
matchExpectation a@(GetReleaseGroupReleases{}) (ApiExpectation _ requestExpectation b@(GetReleaseGroupReleases{}) resp) = checkResult requestExpectation a b resp
matchExpectation a@(GetPolicies{}) (ApiExpectation _ requestExpectation b@(GetPolicies{}) resp) = checkResult requestExpectation a b resp
matchExpectation a@(GetTeams{}) (ApiExpectation _ requestExpectation b@(GetTeams{}) resp) = checkResult requestExpectation a b resp
+matchExpectation a@(GetProjectV2{}) (ApiExpectation _ requestExpectation b@(GetProjectV2{}) resp) = checkResult requestExpectation a b resp
+matchExpectation a@(UpdateProject{}) (ApiExpectation _ requestExpectation b@(UpdateProject{}) resp) = checkResult requestExpectation a b resp
+matchExpectation a@(UpdateRevision{}) (ApiExpectation _ requestExpectation b@(UpdateRevision{}) resp) = checkResult requestExpectation a b resp
+matchExpectation a@(GetOrgLabels{}) (ApiExpectation _ requestExpectation b@(GetOrgLabels{}) resp) = checkResult requestExpectation a b resp
+matchExpectation a@(AddTeamProjects{}) (ApiExpectation _ requestExpectation b@(AddTeamProjects{}) resp) = checkResult requestExpectation a b resp
matchExpectation _ _ = Nothing
-- | Handles a request in the context of the mock API.