Skip to content

Commit

Permalink
address pr comments
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffreyHuynh1 committed Apr 24, 2024
1 parent daf2c97 commit 48ee015
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 69 deletions.
9 changes: 7 additions & 2 deletions docs/references/files/fossa-yml.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ project:
locator: custom+1/github.com/fossas/fossa-cli
id: github.com/fossas/fossa-cli
name: fossa-cli
team: cli-team
teams:
- cli-team
- cli-team-1
- cli-team-2
policy: custom-cli-policy
link: fossa.com
url: github.com/fossas/fossa-cli
Expand Down Expand Up @@ -148,11 +150,14 @@ Default:
#### `project.name:`
The name field sets the projects visible name in the FOSSA dashboard. By default, this will be set to the project's ID.

#### `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, commands such as `fossa analyze` and `fossa container analyze` will only use the first team in the list. Use [fossa project edit](../subcommands/project/edit.md) to associate a project to all teams in the list.
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.
Expand Down
5 changes: 5 additions & 0 deletions docs/references/files/fossa-yml.v3.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
"minLength": 1,
"description": "The name field sets the projects visible name in the FOSSA dashboard. By default, this will be set to the project's ID."
},
"team": {
"type": "string",
"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.",
Expand Down
23 changes: 20 additions & 3 deletions docs/references/subcommands/project/edit.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

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.
Expand All @@ -19,13 +20,29 @@ Argument | Required | Description
> 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).
>NOTE: 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.
## .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`.
> 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

Expand Down
27 changes: 15 additions & 12 deletions src/App/Fossa/ApiUtils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module App.Fossa.ApiUtils (
) where

import Control.Algebra (Has)
import Control.Effect.Diagnostics (Diagnostics, errHelp, fatalText, warn)
import Control.Effect.Diagnostics (Diagnostics, errHelp, fatalText)
import Data.Map qualified as Map
import Data.Maybe (mapMaybe)
import Data.Text (Text, intercalate)
Expand Down Expand Up @@ -42,22 +42,25 @@ retrieveTeamIds teamNames teams = do
let missingTeamNames = filter (`Map.notMember` teamMap) teamNames
fatalText $ "Teams " <> intercalate "," missingTeamNames <> "not found"

retrieveLabelIds :: Has Diagnostics sig m => [Text] -> Labels -> m [Int]
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]
go _ [] acc = pure acc
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
warn $
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"
]

go labelMap xs acc
(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)
12 changes: 3 additions & 9 deletions src/App/Fossa/Config/ConfigFile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ import Data.Foldable (asum)
import Data.Functor (($>))
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Maybe (listToMaybe)
import Data.Set (Set)
import Data.Set qualified as Set
import Data.String.Conversion (ToString (toString), ToText (toText))
Expand Down Expand Up @@ -183,19 +182,12 @@ mergeFileCmdMetadata meta cfgFile =
, projectUrl = projectUrl meta <|> (configProject cfgFile >>= configUrl)
, projectJiraKey = projectJiraKey meta <|> (configProject cfgFile >>= configJiraKey)
, projectLink = projectLink meta <|> (configProject cfgFile >>= configLink)
, -- The config file now allows allows the `project` field to accept a list of teams.
-- `fossa project edit` uses the list of teams to add a project to all teams.
-- All other command flows that use the ProjectMetadata type (e.g. `fossa analyze`) does not account for the change so just take the first team in the list if it exists.
projectTeam = projectTeam meta <|> extractFirstTeamFromConfig (configProject cfgFile >>= configTeams)
, projectTeam = projectTeam meta <|> (configProject cfgFile >>= configTeam)
, projectPolicy = policy
, projectLabel = projectLabel meta <|> (maybe [] configLabel (configProject cfgFile))
, projectReleaseGroup = projectReleaseGroup meta <|> (configProject cfgFile >>= configProjectReleaseGroup)
}

extractFirstTeamFromConfig :: Maybe [Text] -> Maybe Text
extractFirstTeamFromConfig Nothing = Nothing
extractFirstTeamFromConfig (Just teams) = listToMaybe teams

data ConfigFile = ConfigFile
{ configVersion :: Int
, configServer :: Maybe Text
Expand All @@ -222,6 +214,7 @@ data ConfigProject = ConfigProject
, configProjID :: Maybe Text
, configName :: Maybe Text
, configLink :: Maybe Text
, configTeam :: Maybe Text
, configTeams :: Maybe [Text]
, configJiraKey :: Maybe Text
, configUrl :: Maybe Text
Expand Down Expand Up @@ -326,6 +319,7 @@ instance FromJSON ConfigProject where
<*> obj .:? "id"
<*> obj .:? "name"
<*> obj .:? "link"
<*> obj .:? "team"
<*> obj .:? "teams"
<*> obj .:? "jiraProjectKey"
<*> obj .:? "url"
Expand Down
6 changes: 4 additions & 2 deletions src/App/Fossa/Config/Project/Edit.hs
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,10 @@ mergeOpts maybeConfig envVars cliOpts@EditOpts{..} = do
, projectPolicy = maybePolicy
, projectLink = projectLinkOpts <|> extractProjectConfigVal maybeConfig configLink
, projectLabels = projectLabelsOpts <|> labelConfigToMaybe configProjectLabels
, -- Teams to add project to
teams = teamsOpts <|> extractProjectConfigVal maybeConfig configTeams
, -- 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]
Expand Down
8 changes: 6 additions & 2 deletions src/App/Fossa/Init/.fossa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,15 @@ version: 3
# # Otherwise, they will be silently ignored. Use fossa project edit to modify this field for existing projects.
# name: fossa-cli
#
# # Name of the teams in your FOSSA organization to associate with this project.
# # 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. Use fossa project edit to modify this field for existing projects.
# team:
# 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
#
Expand Down
81 changes: 45 additions & 36 deletions src/App/Fossa/Project/Edit.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ 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)
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, when)
import Control.Monad (void)
import Data.Foldable (traverse_)
import Data.Maybe (isJust)
import Data.String.Conversion (toText)
Expand Down Expand Up @@ -44,11 +44,15 @@ editMain EditConfig{..} = do

project <- getProjectV2 projectLocator

attemptToUpdateProjectMetadata projectLocator (projectDefaultBranch project) $ projectIssueTrackerIds project
maybeWarnings <- attemptToUpdateProjectMetadata projectLocator (projectDefaultBranch project) $ projectIssueTrackerIds project
attemptToAddProjectToTeams projectLocator teams
attemptToUpdateRevisionMetadata projectLocator (projectLatestRevision project)

logStdout $ "Project " <> "`" <> projectLocator <> "` has been updated." <> "\n"
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
Expand All @@ -62,42 +66,47 @@ editMain EditConfig{..} = do
Text ->
Maybe Text ->
Maybe [Text] ->
m ()
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
when (or [isJust projectTitle, isJust projectUrl, isJust projectJiraKey, isJust projectLabels, isJust projectPolicy]) $ 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
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 <- case projectLabels of
Nothing -> pure Nothing
Just labels -> context "Retrieving label Ids" $ Just <$> retrieveLabelIds (labels) orgLabels
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)
-- 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 =
Expand Down
9 changes: 6 additions & 3 deletions test/App/Fossa/ApiUtilsSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,13 @@ retrieveLabelIdsSpec = do
let label2 = Label 2 "example-label-2"
let labels = Labels [label1, label2]

it' "should return empty list when no label names exists Labels" $ do
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
res `shouldBe'` []
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]
res `shouldBe'` ([2, 1], Nothing)
1 change: 1 addition & 0 deletions test/App/Fossa/Configuration/ConfigurationSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ expectedConfigProject =
, 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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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
Expand Down

0 comments on commit 48ee015

Please sign in to comment.