drone/app/services/migrate/label.go

265 lines
7.3 KiB
Go

// Copyright 2023 Harness, 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 migrate
import (
"context"
"fmt"
"strings"
"time"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/errors"
gitness_store "github.com/harness/gitness/store"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/lucasb-eyer/go-colorful"
"github.com/rs/zerolog/log"
)
const defaultLabelValueColor = enum.LabelColorGreen
// Label is label migrate.
type Label struct {
labelStore store.LabelStore
labelValueStore store.LabelValueStore
spaceStore store.SpaceStore
tx dbtx.Transactor
}
func NewLabel(
labelStore store.LabelStore,
labelValueStore store.LabelValueStore,
spaceStore store.SpaceStore,
tx dbtx.Transactor,
) *Label {
return &Label{
labelStore: labelStore,
labelValueStore: labelValueStore,
spaceStore: spaceStore,
tx: tx,
}
}
//nolint:gocognit
func (migrate Label) Import(
ctx context.Context,
migrator types.Principal,
space *types.Space,
extLabels []*ExternalLabel,
) ([]*types.Label, error) {
labels := make([]*types.Label, len(extLabels))
labelValues := make(map[string][]string)
spaceIDs, err := migrate.spaceStore.GetAncestorIDs(ctx, space.ID)
if err != nil {
return nil, fmt.Errorf("failed to get space ids hierarchy: %w", err)
}
scope := int64(len(spaceIDs))
for i, extLabel := range extLabels {
label, err := convertLabelWithSanitization(ctx, migrator, space.ID, scope, *extLabel)
if err != nil {
return nil, fmt.Errorf("failed to sanitize and convert external label input: %w", err)
}
labels[i] = label
if extLabel.Value != "" {
valueIn := &types.DefineValueInput{
Value: extLabel.Value,
Color: defaultLabelValueColor,
}
if err := valueIn.Sanitize(); err != nil {
return nil, fmt.Errorf("failed to sanitize external label value input: %w", err)
}
labelValues[label.Key] = append(labelValues[label.Key], valueIn.Value)
}
}
err = migrate.tx.WithTx(ctx, func(ctx context.Context) error {
for _, label := range labels {
err := migrate.defineLabelsAndValues(ctx, migrator.ID, space.ID, label, labelValues[label.Key])
if err != nil {
return fmt.Errorf("failed to define labels and/or values: %w", err)
}
}
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to define external labels: %w", err)
}
return labels, nil
}
func (migrate Label) defineLabelsAndValues(
ctx context.Context,
migratorID int64,
spaceID int64,
labelIn *types.Label,
extValues []string) error {
var label *types.Label
var err error
// try to find the label first as it might have been defined already.
label, err = migrate.labelStore.Find(ctx, &spaceID, nil, labelIn.Key)
if errors.Is(err, gitness_store.ErrResourceNotFound) {
err := migrate.labelStore.Define(ctx, labelIn)
if err != nil {
return fmt.Errorf("failed to define label: %w", err)
}
label = labelIn
} else if err != nil {
return fmt.Errorf("failed to find the label: %w", err)
}
values, err := migrate.labelValueStore.List(ctx, label.ID, &types.ListQueryFilter{})
if err != nil {
return fmt.Errorf("failed to list label values: %w", err)
}
now := time.Now().UnixMilli()
existingValues := make(map[string]bool)
for _, val := range values {
existingValues[val.Value] = true
}
var newValuesCount int
for _, val := range extValues {
if existingValues[val] {
continue
}
// define new label values
if err := migrate.labelValueStore.Define(ctx, &types.LabelValue{
LabelID: label.ID,
Value: val,
Color: defaultLabelValueColor,
Created: now,
Updated: now,
CreatedBy: migratorID,
UpdatedBy: migratorID,
}); err != nil {
return fmt.Errorf("failed to create label value: %w", err)
}
newValuesCount++
}
_, err = migrate.labelStore.IncrementValueCount(ctx, label.ID, newValuesCount)
if err != nil {
return fmt.Errorf("failed to update label value count: %w", err)
}
return nil
}
func convertLabelWithSanitization(
ctx context.Context,
migrator types.Principal,
spaceID int64,
scope int64,
extLabel ExternalLabel,
) (*types.Label, error) {
in := &types.DefineLabelInput{
Key: extLabel.Name,
Type: enum.LabelTypeStatic,
Description: extLabel.Description,
Color: findClosestColor(ctx, extLabel.Color),
}
if err := in.Sanitize(); err != nil {
return nil, fmt.Errorf("failed to sanitize external labels input: %w", err)
}
now := time.Now().UnixMilli()
label := &types.Label{
SpaceID: &spaceID,
RepoID: nil,
Scope: scope,
Key: in.Key,
Color: in.Color,
Description: in.Description,
Type: in.Type,
Created: now,
Updated: now,
CreatedBy: migrator.ID,
UpdatedBy: migrator.ID,
}
return label, nil
}
// findClosestColor finds the visually closest color to a provided value using go-colorful library.
func findClosestColor(ctx context.Context, extColor string) enum.LabelColor {
supportedColors, defColor := enum.GetAllLabelColors()
if len(extColor) > 1 && string(extColor[0]) != "#" {
extColor = "#" + extColor
}
targetColor, err := colorful.Hex(strings.ToUpper(extColor))
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msg("failed to convert the color to hex. choosing default color instead.")
return defColor
}
closestColor := supportedColors[0]
minDistance := targetColor.DistanceLab(convertToColorful(supportedColors[0]))
for _, labelColor := range supportedColors[1:] {
distance := targetColor.DistanceLab(convertToColorful(labelColor))
if distance < minDistance {
closestColor = labelColor
minDistance = distance
}
}
return closestColor
}
// convertToColorful converts Gitness supported label colors to the hex (text value) using web/src/utils:ColorDetails.
func convertToColorful(color enum.LabelColor) colorful.Color {
var hexColor colorful.Color
switch color {
case enum.LabelColorRed:
hexColor, _ = colorful.Hex("#C7292F")
case enum.LabelColorGreen:
hexColor, _ = colorful.Hex("#16794C")
case enum.LabelColorYellow:
hexColor, _ = colorful.Hex("#92582D")
case enum.LabelColorBlue:
hexColor, _ = colorful.Hex("#236E93")
case enum.LabelColorPink:
hexColor, _ = colorful.Hex("#C41B87")
case enum.LabelColorPurple:
hexColor, _ = colorful.Hex("#9C2AAD")
case enum.LabelColorViolet:
hexColor, _ = colorful.Hex("#5645AF")
case enum.LabelColorIndigo:
hexColor, _ = colorful.Hex("#3250B2")
case enum.LabelColorCyan:
hexColor, _ = colorful.Hex("#0B7792")
case enum.LabelColorOrange:
hexColor, _ = colorful.Hex("#995137")
case enum.LabelColorBrown:
hexColor, _ = colorful.Hex("#805C43")
case enum.LabelColorMint:
hexColor, _ = colorful.Hex("#247469")
case enum.LabelColorLime:
hexColor, _ = colorful.Hex("#586729")
default:
// blue is the default color on Gitness
hexColor, _ = colorful.Hex("#236E93")
}
return hexColor
}