Skip to content

Commit

Permalink
CLOUDP-225860: add new changelog convert command to create a slack su…
Browse files Browse the repository at this point in the history
…mmary (#197)
  • Loading branch information
andreaangiolillo authored Aug 28, 2024
1 parent 1f15604 commit b937a4b
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 10 deletions.
4 changes: 2 additions & 2 deletions tools/cli/internal/changelog/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ func newVersionedPaths(paths []*Path, version string) []*Path {
func newChangelog(baseMetadata, revisionMetadata *Metadata, exceptionFilePath string, baseChangelog []*Entry) (*Changelog, error) {
var err error
if baseChangelog == nil {
baseChangelog, err = newEntriesFromPath(fmt.Sprintf("%s/%s", baseMetadata.Path, "changelog.json"))
baseChangelog, err = NewEntriesFromPath(fmt.Sprintf("%s/%s", baseMetadata.Path, "changelog.json"))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -340,7 +340,7 @@ func newChangelog(baseMetadata, revisionMetadata *Metadata, exceptionFilePath st
}, nil
}

func newEntriesFromPath(path string) ([]*Entry, error) {
func NewEntriesFromPath(path string) ([]*Entry, error) {
contents, err := os.ReadFile(path)
if err != nil {
return nil, err
Expand Down
12 changes: 6 additions & 6 deletions tools/cli/internal/changelog/merge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
)

func TestMergeChangelogOneChange(t *testing.T) {
baseChangelog, err := newEntriesFromPath("../../test/data/changelog/changelog.json")
baseChangelog, err := NewEntriesFromPath("../../test/data/changelog/changelog.json")
require.NoError(t, err)

lastChangelogRunDate := baseChangelog[0].Date
Expand Down Expand Up @@ -99,7 +99,7 @@ func TestMergeChangelogOneChange(t *testing.T) {

func TestMergeChangelogTwoVersionsNoDeprecations(t *testing.T) {
// arrange
baseChangelog, err := newEntriesFromPath("../../test/data/changelog/changelog.json")
baseChangelog, err := NewEntriesFromPath("../../test/data/changelog/changelog.json")
require.NoError(t, err)

lastChangelogRunDate := baseChangelog[0].Date
Expand Down Expand Up @@ -215,7 +215,7 @@ func TestMergeChangelogTwoVersionsNoDeprecations(t *testing.T) {
}

func TestMergeChangelogAddTwoEndpoints(t *testing.T) {
originalChangelog, err := newEntriesFromPath("../../test/data/changelog/changelog.json")
originalChangelog, err := NewEntriesFromPath("../../test/data/changelog/changelog.json")
require.NoError(t, err)

lastChangelogRunDate := originalChangelog[0].Date
Expand Down Expand Up @@ -468,7 +468,7 @@ func TestSortChangelog(t *testing.T) {

func TestMergeChangelogTwoVersionsWithDeprecations(t *testing.T) {
// arrange
baseChangelog, err := newEntriesFromPath("../../test/data/changelog/changelog.json")
baseChangelog, err := NewEntriesFromPath("../../test/data/changelog/changelog.json")
require.NoError(t, err)

lastChangelogRunDate := baseChangelog[0].Date
Expand Down Expand Up @@ -596,7 +596,7 @@ func TestMergeChangelogTwoVersionsWithDeprecations(t *testing.T) {
}

func TestMergeChangelogWithDeprecations(t *testing.T) {
baseChangelog, err := newEntriesFromPath("../../test/data/changelog/changelog.json")
baseChangelog, err := NewEntriesFromPath("../../test/data/changelog/changelog.json")
require.NoError(t, err)

firstVersion := "2023-02-01"
Expand Down Expand Up @@ -718,7 +718,7 @@ func TestMergeChangelogWithDeprecations(t *testing.T) {
}

func TestMergeChangelogCompare(t *testing.T) {
baseChangelog, err := newEntriesFromPath("../../test/data/changelog/changelog.json")
baseChangelog, err := NewEntriesFromPath("../../test/data/changelog/changelog.json")
require.NoError(t, err)

firstVersion := "2023-01-01"
Expand Down
2 changes: 2 additions & 0 deletions tools/cli/internal/cli/changelog/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package changelog

import (
"github.com/mongodb/openapi/tools/cli/internal/cli/changelog/convert"
"github.com/mongodb/openapi/tools/cli/internal/cli/changelog/metadata"
"github.com/spf13/cobra"
)
Expand All @@ -31,6 +32,7 @@ func Builder() *cobra.Command {
cmd.AddCommand(
CreateBuilder(),
metadata.Builder(),
convert.Builder(),
)

return cmd
Expand Down
2 changes: 1 addition & 1 deletion tools/cli/internal/cli/changelog/changelog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestBuilder(t *testing.T) {
test.CmdValidator(
t,
Builder(),
2,
3,
[]string{},
)
}
33 changes: 33 additions & 0 deletions tools/cli/internal/cli/changelog/convert/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2024 MongoDB Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package convert

import (
"github.com/spf13/cobra"
)

func Builder() *cobra.Command {
cmd := &cobra.Command{
Use: "convert",
Short: "Convert API Changelog entries into another format.",
Annotations: map[string]string{
"toc": "true",
},
}

cmd.AddCommand(SlackBuilder())

return cmd
}
183 changes: 183 additions & 0 deletions tools/cli/internal/cli/changelog/convert/slack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright 2024 MongoDB Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package convert

import (
"encoding/json"
"fmt"
"sort"
"strconv"

"github.com/mongodb/openapi/tools/cli/internal/changelog"
"github.com/mongodb/openapi/tools/cli/internal/cli/flag"
"github.com/mongodb/openapi/tools/cli/internal/cli/usage"
"github.com/spf13/cobra"
)

const (
backwardCompatibleColor = "#47a249"
notBackwardCompatibleColor = "#b51818"
parseFull = "full"
attachmentTypeDefault = "default"
)

// Message represents the overall structure of the SLACK message JSON.
type Message struct {
Channel string `json:"channel"`
ThreadTS string `json:"thread_ts,omitempty"`
Parse string `json:"parse"`
Attachments []*Attachment `json:"attachments"`
}

// Attachment represents the structure of each attachment in the JSON.
type Attachment struct {
Text string `json:"text"`
Color string `json:"color"`
AttachmentType string `json:"attachment_type"`
}

type SlackOpts struct {
path string
messageID string
channelID string
}

func (o *SlackOpts) Run() error {
entries, err := changelog.NewEntriesFromPath(o.path)
if err != nil {
return err
}

message := o.generateMessage(entries)
metadataBytes, err := json.MarshalIndent(message, "", " ")
if err != nil {
return err
}

fmt.Println(string(metadataBytes))
return nil
}

func (o *SlackOpts) generateMessage(entries []*changelog.Entry) []*Message {
attachments := make([]*Attachment, 0)
for _, entry := range entries {
for _, path := range entry.Paths {
for _, version := range path.Versions {
attachments = append(attachments, newAttachmentFromVersion(path, version)...)
}
}
}

return newMessagesFromAttachments(orderAttachments(attachments), o.channelID, o.messageID)
}

// newMessagesFromAttachments creates a slice of messages from the attachments.
// Slack API has a limit of 100 attachments per message, so we need to split the attachments into multiple messages.
func newMessagesFromAttachments(attachments []*Attachment, channelID, messageID string) []*Message {
batchSize := 100
numAttachments := len(attachments)
if numAttachments <= batchSize {
return []*Message{
{
Channel: channelID,
ThreadTS: messageID,
Parse: parseFull,
Attachments: attachments,
},
}
}

numBatches := numAttachments / batchSize
messages := make([]*Message, 0)
for i := 0; i < numBatches; i++ {
start := i * batchSize
end := (i + 1) * batchSize
if end > numAttachments {
end = numAttachments
}

batchAttachments := attachments[start:end]
message := &Message{
Channel: channelID,
ThreadTS: messageID,
Parse: parseFull,
Attachments: batchAttachments,
}
messages = append(messages, message)
}

return messages
}

// orderAttachments orders the attachments by backward compatibility.
// The attachments that are not backward compatible are shown first.
func orderAttachments(attachments []*Attachment) []*Attachment {
sort.Slice(attachments, func(i, j int) bool {
return attachments[i].Color == notBackwardCompatibleColor && attachments[j].Color != notBackwardCompatibleColor
})
return attachments
}

func newAttachmentFromVersion(path *changelog.Path, version *changelog.Version) []*Attachment {
attachments := make([]*Attachment, 0)
for _, change := range version.Changes {
attachments = append(attachments, newAttachmentFromChange(version.Version, path.HTTPMethod, path.URI, change))
}

return attachments
}

func newAttachmentFromChange(version, method, path string, change *changelog.Change) *Attachment {
return &Attachment{
Text: newAttachmentText(version, method, path, change.Code, change.Description, strconv.FormatBool(change.BackwardCompatible)),
Color: newColorFromBackwardCompatible(change.BackwardCompatible),
AttachmentType: attachmentTypeDefault,
}
}

func newAttachmentText(version, method, path, changeCode, change, backwardCompatible string) string {
return fmt.Sprintf("\n• *Version*: `%s`\n• *Path*: `%s %s`\n• *Change Code*: `%s`\n• *Change*: `%s`\n• *Backward Compatible*: `%s`",
version, method, path, changeCode, change, backwardCompatible)
}

func newColorFromBackwardCompatible(backwardCompatible bool) string {
if backwardCompatible {
return backwardCompatibleColor
}
return notBackwardCompatibleColor
}

// SlackBuilder constructs the command for converting the changelog entries into a format that can be used with Slack APIs.
// changelog convert slack -p path_to_changelog -m message_id 1503435956.000247 -c channel_id C061EG9SL
func SlackBuilder() *cobra.Command {
opts := &SlackOpts{}

cmd := &cobra.Command{
Use: "slack -b path_to_changelo -m message_id -c channel_id",
Aliases: []string{"generate"},
Short: "Convert the changelog entries into a format that can be used with Slack APIs.",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
return opts.Run()
},
}

cmd.Flags().StringVarP(&opts.path, flag.Path, flag.PathShort, "", usage.Path)
cmd.Flags().StringVarP(&opts.channelID, flag.ChannelID, flag.ChannelIDShort, "", usage.SlackChannelID)
cmd.Flags().StringVar(&opts.messageID, flag.MessageID, "", usage.MessageID)
_ = cmd.MarkFlagRequired(flag.Path)
_ = cmd.MarkFlagRequired(flag.ChannelID)
return cmd
}
Loading

0 comments on commit b937a4b

Please sign in to comment.