Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: detect flag extinctions #73

Merged
merged 35 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e175442
add option
jazanne Nov 2, 2023
89bc7b6
try to detect extinctions
jazanne Nov 2, 2023
1f108d3
use skull
jazanne Nov 2, 2023
5fbddf0
gen docs
jazanne Nov 2, 2023
ed55866
update test data
jazanne Nov 2, 2023
d4656b5
rename
jazanne Nov 3, 2023
5d3e745
refactor
jazanne Nov 3, 2023
bf4943a
move to builder pattern for extinctions
jazanne Nov 3, 2023
8bf8973
log err
jazanne Nov 13, 2023
7b1e130
start updating comment
jazanne Nov 13, 2023
5bda4fd
rename some things
jazanne Nov 13, 2023
524fa4d
add extinct flag test
jazanne Nov 13, 2023
21cdd53
add break test
jazanne Nov 13, 2023
cfdbf72
import redundancies
jazanne Nov 13, 2023
889026d
move/rename pkg
jazanne Nov 13, 2023
3c5aad3
rename
jazanne Nov 13, 2023
de09d48
update tests
jazanne Nov 13, 2023
a55d880
if
jazanne Nov 13, 2023
6cec40c
add helper func
jazanne Nov 13, 2023
8d59986
some renaming and cleanup
jazanne Nov 13, 2023
e31eb93
just a test
jazanne Nov 13, 2023
c2db611
conditional
jazanne Nov 13, 2023
465b18e
Revert "just a test"
jazanne Nov 13, 2023
a9570af
another conditional
jazanne Nov 13, 2023
9502c7f
Revert "Revert "just a test""
jazanne Nov 13, 2023
701b4f6
Revert "Revert "Revert "just a test"""
jazanne Nov 13, 2023
5469761
update test
jazanne Nov 13, 2023
8e1bc0f
more handling for include extinctions
jazanne Nov 13, 2023
abc98c0
cleanup
jazanne Nov 13, 2023
60c8d7a
add test data
jazanne Nov 13, 2023
93ec5bf
set output and fix wrong one
jazanne Nov 13, 2023
144e001
rename
jazanne Nov 13, 2023
9713925
fix typo
jazanne Nov 13, 2023
8153685
init cfg with defaults
jazanne Nov 13, 2023
e390225
docs
jazanne Nov 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ This action does not support monorepos or searching for flags across LaunchDarkl
| include-archived-flags | Scan for archived flags | `false` | true |
| max-flags | Maximum number of flags to find per PR | `false` | 5 |
| base-uri | The base URI for the LaunchDarkly server. Most users should use the default value. | `false` | https://app.launchdarkly.com |
| check-extinctions | Check if removed flags still exist in codebase | `false` | true |
<!-- action-docs-inputs -->

<!-- action-docs-outputs -->
Expand All @@ -117,4 +118,7 @@ This action does not support monorepos or searching for flags across LaunchDarkl
| any-changed | Returns true if any flags have been changed in PR |
| changed-flags | Space-separated list of flags changed in PR |
| changed-flags-count | Number of flags changed in PR |
| any-extinct | Returns true if any flags have been removed in PR and no longer exist in codebase. Only returned if `check-extinctions` is true. |
| extinct-flags | Space-separated list of flags removed in PR and no longer exist in codebase. Only returned if `check-extinctions` is true. |
| extinct-flags-count | Number of flags removed in PR and no longer exist in codebase. Only returned if `check-extinctions` is true. |
<!-- action-docs-outputs -->
10 changes: 10 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ inputs:
description: The base URI for the LaunchDarkly server. Most users should use the default value.
required: false
default: 'https://app.launchdarkly.com'
check-extinctions:
description: Check if removed flags still exist in codebase
required: false
default: 'true'

outputs:
any-modified:
Expand All @@ -61,3 +65,9 @@ outputs:
description: Space-separated list of flags changed in PR
changed-flags-count:
description: Number of flags changed in PR
any-extinct:
description: Returns true if any flags have been removed in PR and no longer exist in codebase. Only returned if `check-extinctions` is true.
extinct-flags:
description: Space-separated list of flags removed in PR and no longer exist in codebase. Only returned if `check-extinctions` is true.
extinct-flags-count:
description: Number of flags removed in PR and no longer exist in codebase. Only returned if `check-extinctions` is true.
55 changes: 25 additions & 30 deletions comments/comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ import (

"github.com/google/go-github/github"
ldapi "github.com/launchdarkly/api-client-go/v13"
"github.com/launchdarkly/find-code-references-in-pull-request/config"
lcr "github.com/launchdarkly/find-code-references-in-pull-request/config"
lflags "github.com/launchdarkly/find-code-references-in-pull-request/flags"
refs "github.com/launchdarkly/find-code-references-in-pull-request/internal/references"
)

type Comment struct {
Flag ldapi.FeatureFlag
ArchivedAt time.Time
Added bool
Extinct bool
Aliases []string
ChangeType string
Primary ldapi.FeatureFlagConfig
Expand All @@ -37,10 +37,11 @@ func isNil(a interface{}) bool {
return a == nil || reflect.ValueOf(a).IsNil()
}

func githubFlagComment(flag ldapi.FeatureFlag, aliases []string, added bool, config *config.Config) (string, error) {
func githubFlagComment(flag ldapi.FeatureFlag, aliases []string, added, extinct bool, config *lcr.Config) (string, error) {
commentTemplate := Comment{
Flag: flag,
Added: added,
Extinct: config.CheckExtinctions && extinct,
Aliases: aliases,
Primary: flag.Environments[config.LdEnvironment],
LDInstance: config.LdInstance,
Expand All @@ -50,15 +51,13 @@ func githubFlagComment(flag ldapi.FeatureFlag, aliases []string, added bool, con
commentTemplate.ArchivedAt = time.UnixMilli(*flag.ArchivedDate)
}
// All whitespace for template is required to be there or it will not render properly nested.
tmplSetup := `| {{- if eq .Flag.Archived true}}{{- if eq .Added true}} :warning:{{- end}}{{- end}}` +
` [{{.Flag.Name}}]({{.LDInstance}}{{.Primary.Site.Href}})` +
`{{- if eq .Flag.Archived true}}` +
` (archived on {{.ArchivedAt | date "2006-01-02"}})` +
`{{- end}} | ` +
tmplSetup := `| [{{.Flag.Name}}]({{.LDInstance}}{{.Primary.Site.Href}}) | ` +
"`" + `{{.Flag.Key}}` + "` |" +
`{{- if ne (len .Aliases) 0}}` +
`{{range $i, $e := .Aliases }}` + `{{if $i}},{{end}}` + " `" + `{{$e}}` + "`" + `{{end}}` +
`{{- end}} |`
`{{- end}} | ` +
`{{- if eq .Extinct true}} :white_check_mark: all references removed{{- end}} ` +
`{{- if eq .Flag.Archived true}}{{- if eq .Extinct true}}<br>{{end}}{{- if eq .Added true}} :warning:{{else}} :information_source:{{- end}} archived on {{.ArchivedAt | date "2006-01-02"}}{{- end}} |`

tmpl := template.Must(template.New("comment").Funcs(template.FuncMap{"trim": strings.TrimSpace, "isNil": isNil}).Funcs(sprig.FuncMap()).Parse(tmplSetup))
err := tmpl.Execute(&commentBody, commentTemplate)
Expand All @@ -85,8 +84,8 @@ type FlagComments struct {
CommentsRemoved []string
}

func BuildFlagComment(buildComment FlagComments, flagsRef lflags.FlagsRef, existingComment *github.IssueComment) string {
tableHeader := "| Name | Key | Aliases found |\n| --- | --- | --- |"
func BuildFlagComment(buildComment FlagComments, flagsRef refs.ReferenceSummary, existingComment *github.IssueComment) string {
tableHeader := "| Name | Key | Aliases found | Info |\n| --- | --- | --- | --- |"

var commentStr []string
commentStr = append(commentStr, "## LaunchDarkly flag references")
Expand Down Expand Up @@ -122,36 +121,32 @@ func BuildFlagComment(buildComment FlagComments, flagsRef lflags.FlagsRef, exist
return postedComments
}

func ProcessFlags(flagsRef lflags.FlagsRef, flags []ldapi.FeatureFlag, config *lcr.Config) FlagComments {
func ProcessFlags(flagsRef refs.ReferenceSummary, flags []ldapi.FeatureFlag, config *lcr.Config) FlagComments {
buildComment := FlagComments{}
addedKeys := make([]string, 0, len(flagsRef.FlagsAdded))
for key := range flagsRef.FlagsAdded {
addedKeys = append(addedKeys, key)
}
// sort keys so hashing can work for checking if comment already exists
sort.Strings(addedKeys)
for _, flagKey := range addedKeys {

for _, flagKey := range flagsRef.AddedKeys() {
flagAliases := flagsRef.FlagsAdded[flagKey]
idx, _ := find(flags, flagKey)
createComment, err := githubFlagComment(flags[idx], flagAliases, true, config)
buildComment.CommentsAdded = append(buildComment.CommentsAdded, createComment)
createComment, err := githubFlagComment(flags[idx], flagAliases, true, false, config)
if err != nil {
log.Println(err)
}
buildComment.CommentsAdded = append(buildComment.CommentsAdded, createComment)
}
removedKeys := make([]string, 0, len(flagsRef.FlagsRemoved))
for key := range flagsRef.FlagsRemoved {
removedKeys = append(removedKeys, key)
}
sort.Strings(removedKeys)
for _, flagKey := range removedKeys {

for _, flagKey := range flagsRef.RemovedKeys() {
flagAliases := flagsRef.FlagsRemoved[flagKey]
idx, _ := find(flags, flagKey)
removedComment, err := githubFlagComment(flags[idx], flagAliases, false, config)
buildComment.CommentsRemoved = append(buildComment.CommentsRemoved, removedComment)
extinct := false
if flagsRef.ExtinctFlags != nil {
_, e := flagsRef.ExtinctFlags[flagKey]
extinct = e
}
removedComment, err := githubFlagComment(flags[idx], flagAliases, false, extinct, config)
if err != nil {
log.Println(err)
}
buildComment.CommentsRemoved = append(buildComment.CommentsRemoved, removedComment)
}

return buildComment
Expand All @@ -166,7 +161,7 @@ func find(slice []ldapi.FeatureFlag, val string) (int, bool) {
return -1, false
}

func uniqueFlagKeys(a, b lflags.FlagAliasMap) []string {
func uniqueFlagKeys(a, b refs.FlagAliasMap) []string {
maxKeys := len(a) + len(b)
allKeys := make([]string, 0, maxKeys)
for k := range a {
Expand Down
75 changes: 47 additions & 28 deletions comments/comments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

ldapi "github.com/launchdarkly/api-client-go/v13"
"github.com/launchdarkly/find-code-references-in-pull-request/config"
lflags "github.com/launchdarkly/find-code-references-in-pull-request/flags"
refs "github.com/launchdarkly/find-code-references-in-pull-request/internal/references"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand All @@ -22,8 +22,9 @@ type testFlagEnv struct {
func newTestAccEnv() *testFlagEnv {
flag := createFlag("example-flag")
config := config.Config{
LdEnvironment: "production",
LdInstance: "https://example.com/",
LdEnvironment: "production",
LdInstance: "https://example.com/",
CheckExtinctions: true,
}

archivedFlag := createFlag("archived-flag")
Expand Down Expand Up @@ -61,17 +62,17 @@ func createFlag(key string) ldapi.FeatureFlag {

type testCommentBuilder struct {
Comments FlagComments
FlagsRef lflags.FlagsRef
FlagsRef refs.ReferenceSummary
}

func newCommentBuilderAccEnv() *testCommentBuilder {
flagComments := FlagComments{
CommentsAdded: []string{},
CommentsRemoved: []string{},
}
flagsAdded := make(lflags.FlagAliasMap)
flagsRemoved := make(lflags.FlagAliasMap)
flagsRef := lflags.FlagsRef{
flagsAdded := make(refs.FlagAliasMap)
flagsRemoved := make(refs.FlagAliasMap)
flagsRef := refs.ReferenceSummary{
FlagsAdded: flagsAdded,
FlagsRemoved: flagsRemoved,
}
Expand All @@ -84,16 +85,16 @@ func newCommentBuilderAccEnv() *testCommentBuilder {

type testProcessor struct {
Flags []ldapi.FeatureFlag
FlagsRef lflags.FlagsRef
FlagsRef refs.ReferenceSummary
Config config.Config
}

func newProcessFlagAccEnv() *testProcessor {
flag := createFlag("example-flag")
flags := []ldapi.FeatureFlag{flag}
flagsAdded := make(lflags.FlagAliasMap)
flagsRemoved := make(lflags.FlagAliasMap)
flagsRef := lflags.FlagsRef{
flagsAdded := make(refs.FlagAliasMap)
flagsRemoved := make(refs.FlagAliasMap)
flagsRef := refs.ReferenceSummary{
FlagsAdded: flagsAdded,
FlagsRemoved: flagsRemoved,
}
Expand All @@ -113,9 +114,9 @@ func newProcessMultipleFlagsFlagAccEnv() *testProcessor {
flag := createFlag("example-flag")
flag2 := createFlag("second-flag")
flags := []ldapi.FeatureFlag{flag, flag2}
flagsAdded := make(lflags.FlagAliasMap)
flagsRemoved := make(lflags.FlagAliasMap)
flagsRef := lflags.FlagsRef{
flagsAdded := make(refs.FlagAliasMap)
flagsRemoved := make(refs.FlagAliasMap)
flagsRef := refs.ReferenceSummary{
FlagsAdded: flagsAdded,
FlagsRemoved: flagsRemoved,
}
Expand All @@ -137,6 +138,8 @@ func TestGithubFlagComment(t *testing.T) {
t.Run("Flag with alias", acceptanceTestEnv.Alias)
t.Run("Archived flag added", acceptanceTestEnv.ArchivedAdded)
t.Run("Archived flag removed", acceptanceTestEnv.ArchivedRemoved)
t.Run("Extinct flag", acceptanceTestEnv.ExtinctFlag)
t.Run("Extinct and Archived flag", acceptanceTestEnv.ExtinctAndArchivedFlag)
}

func TestProcessFlags(t *testing.T) {
Expand Down Expand Up @@ -164,34 +167,50 @@ func TestBuildFlagComment(t *testing.T) {
}

func (e *testFlagEnv) NoAliases(t *testing.T) {
comment, err := githubFlagComment(e.Flag, []string{}, true, &e.Config)
comment, err := githubFlagComment(e.Flag, []string{}, true, false, &e.Config)
require.NoError(t, err)

expected := "| [example flag](https://example.com/test) | `example-flag` | |"
expected := "| [example flag](https://example.com/test) | `example-flag` | | |"
assert.Equal(t, expected, comment)
}

func (e *testFlagEnv) Alias(t *testing.T) {
comment, err := githubFlagComment(e.Flag, []string{"exampleFlag", "ExampleFlag"}, true, &e.Config)
comment, err := githubFlagComment(e.Flag, []string{"exampleFlag", "ExampleFlag"}, true, false, &e.Config)
require.NoError(t, err)

expected := "| [example flag](https://example.com/test) | `example-flag` | `exampleFlag`, `ExampleFlag` |"
expected := "| [example flag](https://example.com/test) | `example-flag` | `exampleFlag`, `ExampleFlag` | |"
assert.Equal(t, expected, comment)
}

func (e *testFlagEnv) ArchivedAdded(t *testing.T) {
comment, err := githubFlagComment(e.ArchivedFlag, []string{}, true, &e.Config)
comment, err := githubFlagComment(e.ArchivedFlag, []string{}, true, false, &e.Config)
require.NoError(t, err)

expected := "| :warning: [archived flag](https://example.com/test) (archived on 2023-08-03) | `archived-flag` | |"
expected := "| [archived flag](https://example.com/test) | `archived-flag` | | :warning: archived on 2023-08-03 |"
assert.Equal(t, expected, comment)
}

func (e *testFlagEnv) ArchivedRemoved(t *testing.T) {
comment, err := githubFlagComment(e.ArchivedFlag, []string{}, false, &e.Config)
comment, err := githubFlagComment(e.ArchivedFlag, []string{}, false, false, &e.Config)
require.NoError(t, err)

expected := "| [archived flag](https://example.com/test) | `archived-flag` | | :information_source: archived on 2023-08-03 |"
assert.Equal(t, expected, comment)
}

func (e *testFlagEnv) ExtinctFlag(t *testing.T) {
comment, err := githubFlagComment(e.Flag, []string{}, false, true, &e.Config)
require.NoError(t, err)

expected := "| [example flag](https://example.com/test) | `example-flag` | | :white_check_mark: all references removed |"
assert.Equal(t, expected, comment)
}

func (e *testFlagEnv) ExtinctAndArchivedFlag(t *testing.T) {
comment, err := githubFlagComment(e.ArchivedFlag, []string{}, false, true, &e.Config)
require.NoError(t, err)

expected := "| [archived flag](https://example.com/test) (archived on 2023-08-03) | `archived-flag` | |"
expected := "| [archived flag](https://example.com/test) | `archived-flag` | | :white_check_mark: all references removed<br> :information_source: archived on 2023-08-03 |"
assert.Equal(t, expected, comment)
}

Expand All @@ -200,7 +219,7 @@ func (e *testCommentBuilder) AddedOnly(t *testing.T) {
e.Comments.CommentsAdded = []string{"comment1", "comment2"}
comment := BuildFlagComment(e.Comments, e.FlagsRef, nil)

expected := "## LaunchDarkly flag references\n### :mag: 1 flag added or modified\n\n| Name | Key | Aliases found |\n| --- | --- | --- |\ncomment1\ncomment2\n\n\n <!-- flags:example-flag -->\n <!-- comment hash: c449ce18623b2038f1ae2f02c46869cd -->"
expected := "## LaunchDarkly flag references\n### :mag: 1 flag added or modified\n\n| Name | Key | Aliases found | Info |\n| --- | --- | --- | --- |\ncomment1\ncomment2\n\n\n <!-- flags:example-flag -->\n <!-- comment hash: 58e2fd003e576dfd9e59d82b7fb20ca4 -->"
assert.Equal(t, expected, comment)
}

Expand All @@ -210,7 +229,7 @@ func (e *testCommentBuilder) RemovedOnly(t *testing.T) {
e.Comments.CommentsRemoved = []string{"comment1", "comment2"}
comment := BuildFlagComment(e.Comments, e.FlagsRef, nil)

expected := "## LaunchDarkly flag references\n### :x: 2 flags removed\n\n| Name | Key | Aliases found |\n| --- | --- | --- |\ncomment1\ncomment2\n <!-- flags:example-flag,sample-flag -->\n <!-- comment hash: 9ab2cb5c0fcfcce9002cf0935f5f4ad5 -->"
expected := "## LaunchDarkly flag references\n### :x: 2 flags removed\n\n| Name | Key | Aliases found | Info |\n| --- | --- | --- | --- |\ncomment1\ncomment2\n <!-- flags:example-flag,sample-flag -->\n <!-- comment hash: acdd50c762ddfa84720067a3b272a032 -->"
assert.Equal(t, expected, comment)
}

Expand All @@ -221,7 +240,7 @@ func (e *testCommentBuilder) AddedAndRemoved(t *testing.T) {
e.Comments.CommentsRemoved = []string{"comment1", "comment2"}
comment := BuildFlagComment(e.Comments, e.FlagsRef, nil)

expected := "## LaunchDarkly flag references\n### :mag: 1 flag added or modified\n\n| Name | Key | Aliases found |\n| --- | --- | --- |\ncomment1\ncomment2\n\n\n### :x: 1 flag removed\n\n| Name | Key | Aliases found |\n| --- | --- | --- |\ncomment1\ncomment2\n <!-- flags:example-flag -->\n <!-- comment hash: 7e6314b3b27a97ed6f8979304650af54 -->"
expected := "## LaunchDarkly flag references\n### :mag: 1 flag added or modified\n\n| Name | Key | Aliases found | Info |\n| --- | --- | --- | --- |\ncomment1\ncomment2\n\n\n### :x: 1 flag removed\n\n| Name | Key | Aliases found | Info |\n| --- | --- | --- | --- |\ncomment1\ncomment2\n <!-- flags:example-flag -->\n <!-- comment hash: 4f891355662b901597e6563a11c15332 -->"

assert.Equal(t, expected, comment)

Expand All @@ -231,7 +250,7 @@ func (e *testProcessor) Basic(t *testing.T) {
e.FlagsRef.FlagsAdded["example-flag"] = []string{}
processor := ProcessFlags(e.FlagsRef, e.Flags, &e.Config)
expected := FlagComments{
CommentsAdded: []string{"| [example flag](https://example.com/test) | `example-flag` | |"},
CommentsAdded: []string{"| [example flag](https://example.com/test) | `example-flag` | | |"},
}
assert.Equal(t, expected, processor)
}
Expand All @@ -242,8 +261,8 @@ func (e *testProcessor) Multi(t *testing.T) {
processor := ProcessFlags(e.FlagsRef, e.Flags, &e.Config)
expected := FlagComments{
CommentsAdded: []string{
"| [example flag](https://example.com/test) | `example-flag` | |",
"| [second flag](https://example.com/test) | `second-flag` | |",
"| [example flag](https://example.com/test) | `example-flag` | | |",
"| [second flag](https://example.com/test) | `second-flag` | | |",
},
}
assert.Equal(t, expected, processor)
Expand Down
16 changes: 13 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Config struct {
MaxFlags int
PlaceholderComment bool
IncludeArchivedFlags bool
CheckExtinctions bool
}

func ValidateInputandParse(ctx context.Context) (*Config, error) {
Expand All @@ -35,8 +36,13 @@ func ValidateInputandParse(ctx context.Context) (*Config, error) {
fmt.Printf("::add-mask::%s\n", repoToken)
}

// set config
var config Config
// init config with defaults
config := Config{
MaxFlags: 5,
IncludeArchivedFlags: true,
CheckExtinctions: true,
}

config.LdProject = os.Getenv("INPUT_PROJECT-KEY")
if config.LdProject == "" {
return nil, errors.New("`project-key` is required")
Expand Down Expand Up @@ -74,12 +80,16 @@ func ValidateInputandParse(ctx context.Context) (*Config, error) {
config.PlaceholderComment = placholderComment
}

config.IncludeArchivedFlags = true
if includeArchivedFlags, err := strconv.ParseBool(os.Getenv("INPUT_INCLUDE-ARCHIVED-FLAGS")); err == nil {
// ignore error - default is true
config.IncludeArchivedFlags = includeArchivedFlags
}

if checkExtinctions, err := strconv.ParseBool(os.Getenv("INPUT_CHECK-EXTINCTIONS")); err == nil {
// ignore error - default is true
config.CheckExtinctions = checkExtinctions
}

config.GHClient = getGithubClient(ctx)
return &config, nil
}
Expand Down
Loading