From 86431fe82f222d2369cd522ca63cba36338074b3 Mon Sep 17 00:00:00 2001 From: Vivek Patel Date: Tue, 26 Apr 2022 16:40:58 +0530 Subject: [PATCH] Add rest endpoints for chart translations It allows saving/updating yAxis label and metric label translations --- .../gocode/locale/$component_service.go.tpl | 37 ++-- .../gocode/locale/$component_types.go.tpl | 11 +- compose/chart.cue | 15 ++ compose/rest.yaml | 26 +++ compose/rest/chart.go | 16 +- compose/rest/handlers/chart.go | 48 ++++- compose/rest/request/chart.go | 166 ++++++++++++++++++ compose/service/chart.go | 51 +++++- compose/service/locale.gen.go | 23 ++- compose/service/locale.go | 39 ++++ compose/types/chart.go | 73 ++++++++ compose/types/locale.gen.go | 44 +++++ pkg/envoy/resource/compose_chart.go | 20 ++- .../resource/resource_translation.gen.go | 12 ++ .../resource_translation_parse.gen.go | 10 ++ ...urce_translation_references_compose.gen.go | 10 ++ .../store/resource_translation_marshal.go | 24 +-- .../yaml/resource_translation_marshal.go | 32 ++-- ...migrations_202203_resource_translations.go | 116 ++++++++++++ pkg/provision/provision.go | 3 + 20 files changed, 713 insertions(+), 63 deletions(-) create mode 100644 pkg/provision/migrations_202203_resource_translations.go diff --git a/codegen/assets/templates/gocode/locale/$component_service.go.tpl b/codegen/assets/templates/gocode/locale/$component_service.go.tpl index 7076f331f..87b53119f 100644 --- a/codegen/assets/templates/gocode/locale/$component_service.go.tpl +++ b/codegen/assets/templates/gocode/locale/$component_service.go.tpl @@ -154,7 +154,6 @@ func (svc resourceTranslationsManager) {{ .expIdent }}(ctx context.Context, {{ r err error out locale.ResourceTranslationSet res *types.{{ .expIdent }} - k types.LocaleKey ) res, err = svc.load{{ .expIdent }}(ctx, svc.store, {{ range .references }}{{ .param }}, {{ end }}) @@ -162,19 +161,29 @@ func (svc resourceTranslationsManager) {{ .expIdent }}(ctx context.Context, {{ r return nil, err } - for _, tag := range svc.locale.Tags() { - {{- range .keys}} - {{- if not .customHandler }} - k = types.{{ .struct }} - out = append(out, &locale.ResourceTranslation{ - Resource: res.ResourceTranslation(), - Lang: tag.String(), - Key: k.Path, - Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), k.Path), - }) - {{ end }} - {{- end}} - } + {{ $customHandlerExist := false }} + {{range .keys}} + {{if not .customHandler}} + {{ $customHandlerExist = true }} + {{end}} + {{end}} + + {{ if $customHandlerExist }} + var k types.LocaleKey + for _, tag := range svc.locale.Tags() { + {{- range .keys}} + {{- if not .customHandler }} + k = types.{{ .struct }} + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: k.Path, + Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), k.Path), + }) + {{ end }} + {{- end}} + } + {{ end }} {{ if .extended }} tmp, err := svc.{{ .ident }}Extended(ctx, res) diff --git a/codegen/assets/templates/gocode/locale/$component_types.go.tpl b/codegen/assets/templates/gocode/locale/$component_types.go.tpl index 13e032264..cb19153c6 100644 --- a/codegen/assets/templates/gocode/locale/$component_types.go.tpl +++ b/codegen/assets/templates/gocode/locale/$component_types.go.tpl @@ -73,7 +73,16 @@ func {{ .expIdent }}ResourceTranslationTpl() string { } func (r *{{ .expIdent }}) DecodeTranslations(tt locale.ResourceTranslationIndex) { - var aux *locale.ResourceTranslation + {{- $decodeFuncExist := false }} + {{- range .keys }} + {{- if not .decodeFunc }} + {{- $decodeFuncExist = true }} + {{- end }} + {{- end}} + + {{- if $decodeFuncExist }} + var aux *locale.ResourceTranslation + {{- end }} {{- range .keys }} {{ if .decodeFunc }} diff --git a/compose/chart.cue b/compose/chart.cue index e35ec31a8..21b43f767 100644 --- a/compose/chart.cue +++ b/compose/chart.cue @@ -43,6 +43,21 @@ chart: schema.#Resource & { } } + locale: { + extended: true + + keys: { + reportsYaxisLabel: { + path: ["yAxis", "label"] + customHandler: true + } + reportsMetricLabel: { + path: ["metrics", {part: "metricID", var: true}, "label"] + customHandler: true + } + } + } + store: { ident: "composeChart" diff --git a/compose/rest.yaml b/compose/rest.yaml index 612de5ce3..3fe18c0af 100644 --- a/compose/rest.yaml +++ b/compose/rest.yaml @@ -1089,6 +1089,32 @@ endpoints: name: chartID required: true title: Chart ID + - name: listTranslations + method: GET + title: List chart translation + path: "/{chartID}/translation" + parameters: + path: + - type: uint64 + name: chartID + required: true + title: ID + - name: updateTranslations + method: PATCH + title: Update chart translation + path: "/{chartID}/translation" + parameters: + path: + - type: uint64 + name: chartID + required: true + title: ID + post: + - name: translations + type: locale.ResourceTranslationSet + title: Resource translation to upsert + required: true + - title: Notifications description: Compose Notifications entrypoint: notification diff --git a/compose/rest/chart.go b/compose/rest/chart.go index 7c3219e74..3b30c7738 100644 --- a/compose/rest/chart.go +++ b/compose/rest/chart.go @@ -37,7 +37,8 @@ type ( Update(ctx context.Context, chart *types.Chart) (*types.Chart, error) DeleteByID(ctx context.Context, namespaceID, chartID uint64) error } - ac chartAccessController + locale service.ResourceTranslationsManagerService + ac chartAccessController } chartAccessController interface { @@ -50,8 +51,9 @@ type ( func (Chart) New() *Chart { return &Chart{ - chart: service.DefaultChart, - ac: service.DefaultAccessControl, + chart: service.DefaultChart, + locale: service.DefaultResourceTranslation, + ac: service.DefaultAccessControl, } } @@ -134,6 +136,14 @@ func (ctrl Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface return api.OK(), ctrl.chart.DeleteByID(ctx, r.NamespaceID, r.ChartID) } +func (ctrl Chart) ListTranslations(ctx context.Context, r *request.ChartListTranslations) (interface{}, error) { + return ctrl.locale.Chart(ctx, r.NamespaceID, r.ChartID) +} + +func (ctrl Chart) UpdateTranslations(ctx context.Context, r *request.ChartUpdateTranslations) (interface{}, error) { + return api.OK(), ctrl.locale.Upsert(ctx, r.Translations) +} + func (ctrl Chart) makePayload(ctx context.Context, c *types.Chart, err error) (*chartPayload, error) { if err != nil || c == nil { return nil, err diff --git a/compose/rest/handlers/chart.go b/compose/rest/handlers/chart.go index f22857b97..fe4f6736e 100644 --- a/compose/rest/handlers/chart.go +++ b/compose/rest/handlers/chart.go @@ -24,15 +24,19 @@ type ( Read(context.Context, *request.ChartRead) (interface{}, error) Update(context.Context, *request.ChartUpdate) (interface{}, error) Delete(context.Context, *request.ChartDelete) (interface{}, error) + ListTranslations(context.Context, *request.ChartListTranslations) (interface{}, error) + UpdateTranslations(context.Context, *request.ChartUpdateTranslations) (interface{}, error) } // HTTP API interface Chart struct { - List func(http.ResponseWriter, *http.Request) - Create func(http.ResponseWriter, *http.Request) - Read func(http.ResponseWriter, *http.Request) - Update func(http.ResponseWriter, *http.Request) - Delete func(http.ResponseWriter, *http.Request) + List func(http.ResponseWriter, *http.Request) + Create func(http.ResponseWriter, *http.Request) + Read func(http.ResponseWriter, *http.Request) + Update func(http.ResponseWriter, *http.Request) + Delete func(http.ResponseWriter, *http.Request) + ListTranslations func(http.ResponseWriter, *http.Request) + UpdateTranslations func(http.ResponseWriter, *http.Request) } ) @@ -116,6 +120,38 @@ func NewChart(h ChartAPI) *Chart { return } + api.Send(w, r, value) + }, + ListTranslations: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewChartListTranslations() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.ListTranslations(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + + api.Send(w, r, value) + }, + UpdateTranslations: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewChartUpdateTranslations() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.UpdateTranslations(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + api.Send(w, r, value) }, } @@ -129,5 +165,7 @@ func (h Chart) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http. r.Get("/namespace/{namespaceID}/chart/{chartID}", h.Read) r.Post("/namespace/{namespaceID}/chart/{chartID}", h.Update) r.Delete("/namespace/{namespaceID}/chart/{chartID}", h.Delete) + r.Get("/namespace/{namespaceID}/chart/{chartID}/translation", h.ListTranslations) + r.Patch("/namespace/{namespaceID}/chart/{chartID}/translation", h.UpdateTranslations) }) } diff --git a/compose/rest/request/chart.go b/compose/rest/request/chart.go index 9fcb13279..6afef7f8b 100644 --- a/compose/rest/request/chart.go +++ b/compose/rest/request/chart.go @@ -12,6 +12,7 @@ import ( "encoding/json" "fmt" "github.com/cortezaproject/corteza-server/pkg/label" + "github.com/cortezaproject/corteza-server/pkg/locale" "github.com/cortezaproject/corteza-server/pkg/payload" "github.com/go-chi/chi/v5" sqlxTypes "github.com/jmoiron/sqlx/types" @@ -160,6 +161,35 @@ type ( // Chart ID ChartID uint64 `json:",string"` } + + ChartListTranslations struct { + // NamespaceID PATH parameter + // + // Namespace ID + NamespaceID uint64 `json:",string"` + + // ChartID PATH parameter + // + // ID + ChartID uint64 `json:",string"` + } + + ChartUpdateTranslations struct { + // NamespaceID PATH parameter + // + // Namespace ID + NamespaceID uint64 `json:",string"` + + // ChartID PATH parameter + // + // ID + ChartID uint64 `json:",string"` + + // Translations POST parameter + // + // Resource translation to upsert + Translations locale.ResourceTranslationSet + } ) // NewChartList request @@ -711,3 +741,139 @@ func (r *ChartDelete) Fill(req *http.Request) (err error) { return err } + +// NewChartListTranslations request +func NewChartListTranslations() *ChartListTranslations { + return &ChartListTranslations{} +} + +// Auditable returns all auditable/loggable parameters +func (r ChartListTranslations) Auditable() map[string]interface{} { + return map[string]interface{}{ + "namespaceID": r.NamespaceID, + "chartID": r.ChartID, + } +} + +// Auditable returns all auditable/loggable parameters +func (r ChartListTranslations) GetNamespaceID() uint64 { + return r.NamespaceID +} + +// Auditable returns all auditable/loggable parameters +func (r ChartListTranslations) GetChartID() uint64 { + return r.ChartID +} + +// Fill processes request and fills internal variables +func (r *ChartListTranslations) Fill(req *http.Request) (err error) { + + { + var val string + // path params + + val = chi.URLParam(req, "namespaceID") + r.NamespaceID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + val = chi.URLParam(req, "chartID") + r.ChartID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + } + + return err +} + +// NewChartUpdateTranslations request +func NewChartUpdateTranslations() *ChartUpdateTranslations { + return &ChartUpdateTranslations{} +} + +// Auditable returns all auditable/loggable parameters +func (r ChartUpdateTranslations) Auditable() map[string]interface{} { + return map[string]interface{}{ + "namespaceID": r.NamespaceID, + "chartID": r.ChartID, + "translations": r.Translations, + } +} + +// Auditable returns all auditable/loggable parameters +func (r ChartUpdateTranslations) GetNamespaceID() uint64 { + return r.NamespaceID +} + +// Auditable returns all auditable/loggable parameters +func (r ChartUpdateTranslations) GetChartID() uint64 { + return r.ChartID +} + +// Auditable returns all auditable/loggable parameters +func (r ChartUpdateTranslations) GetTranslations() locale.ResourceTranslationSet { + return r.Translations +} + +// Fill processes request and fills internal variables +func (r *ChartUpdateTranslations) Fill(req *http.Request) (err error) { + + if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") { + err = json.NewDecoder(req.Body).Decode(r) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return fmt.Errorf("error parsing http request body: %w", err) + } + } + + { + // Caching 32MB to memory, the rest to disk + if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart { + return err + } else if err == nil { + // Multipart params + + } + } + + { + if err = req.ParseForm(); err != nil { + return err + } + + // POST params + + //if val, ok := req.Form["translations[]"]; ok && len(val) > 0 { + // r.Translations, err = locale.ResourceTranslationSet(val), nil + // if err != nil { + // return err + // } + //} + } + + { + var val string + // path params + + val = chi.URLParam(req, "namespaceID") + r.NamespaceID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + val = chi.URLParam(req, "chartID") + r.ChartID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + } + + return err +} diff --git a/compose/service/chart.go b/compose/service/chart.go index fbdd87550..a94119eb9 100644 --- a/compose/service/chart.go +++ b/compose/service/chart.go @@ -2,7 +2,9 @@ package service import ( "context" + "github.com/cortezaproject/corteza-server/pkg/locale" "reflect" + "strconv" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/actionlog" @@ -17,9 +19,11 @@ type ( actionlog actionlog.Recorder ac chartAccessController store store.Storer + locale ResourceTranslationsManagerService } chartAccessController interface { + CanManageResourceTranslations(ctx context.Context) bool CanSearchChartsOnNamespace(context.Context, *types.Namespace) bool CanReadNamespace(context.Context, *types.Namespace) bool CanCreateChartOnNamespace(context.Context, *types.Namespace) bool @@ -44,12 +48,14 @@ func Chart() *chart { ac: DefaultAccessControl, actionlog: DefaultActionlog, store: DefaultStore, + locale: DefaultResourceTranslation, } } func (svc chart) Find(ctx context.Context, filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) { var ( aProps = &chartActionProps{filter: &filter} + ns *types.Namespace ) // For each fetched item, store backend will check if it is valid or not @@ -62,7 +68,7 @@ func (svc chart) Find(ctx context.Context, filter types.ChartFilter) (set types. } err = func() error { - ns, err := loadNamespace(ctx, svc.store, filter.NamespaceID) + ns, err = loadNamespace(ctx, svc.store, filter.NamespaceID) if err != nil { return err } @@ -99,6 +105,13 @@ func (svc chart) Find(ctx context.Context, filter types.ChartFilter) (set types. return err } + // i18n + tag := locale.GetAcceptLanguageFromContext(ctx) + set.Walk(func(p *types.Chart) error { + p.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, p.ResourceTranslation())) + return nil + }) + return nil }() @@ -158,10 +171,23 @@ func (svc chart) Create(ctx context.Context, new *types.Chart) (*types.Chart, er new.UpdatedAt = nil new.DeletedAt = nil + // Ensure chart report IDs + for i, report := range new.Config.Reports { + new.Config.Reports[i].ReportID = nextID() + // Ensure chart report metric IDs + for j := range report.Metrics { + new.Config.Reports[i].Metrics[j]["metricID"] = strconv.FormatUint(nextID(), 10) + } + } + if err = store.CreateComposeChart(ctx, s, new); err != nil { return err } + if err = updateTranslations(ctx, svc.ac, svc.locale, new.EncodeTranslations()...); err != nil { + return + } + if err = label.Create(ctx, s, new); err != nil { return } @@ -249,6 +275,10 @@ func (svc chart) updater(ctx context.Context, namespaceID, chartID uint64, actio } } + if err = updateTranslations(ctx, svc.ac, svc.locale, c.EncodeTranslations()...); err != nil { + return + } + if changes&chartLabelsChanged > 0 { if err = label.Update(ctx, s, c); err != nil { return @@ -304,6 +334,25 @@ func (svc chart) handleUpdate(ctx context.Context, upd *types.Chart) chartUpdate res.Config = upd.Config } + // Assure ReportIDs + for i, r := range res.Config.Reports { + if r.ReportID == 0 { + r.ReportID = nextID() + res.Config.Reports[i] = r + + changes |= chartChanged + } + + // Ensure chart report metric IDs + for j, m := range r.Metrics { + if val, ok := m["metricID"]; !ok || val == 0 { + m["metricID"] = strconv.FormatUint(nextID(), 10) + res.Config.Reports[i].Metrics[j] = m + + changes |= chartChanged + } + } + } if changes&chartChanged > 0 { res.UpdatedAt = now() } diff --git a/compose/service/locale.gen.go b/compose/service/locale.gen.go index 9ed4899d3..014f7841d 100644 --- a/compose/service/locale.gen.go +++ b/compose/service/locale.gen.go @@ -37,6 +37,7 @@ type ( } ResourceTranslationsManagerService interface { + Chart(ctx context.Context, namespaceID uint64, id uint64) (locale.ResourceTranslationSet, error) Module(ctx context.Context, namespaceID uint64, id uint64) (locale.ResourceTranslationSet, error) Namespace(ctx context.Context, id uint64) (locale.ResourceTranslationSet, error) Page(ctx context.Context, namespaceID uint64, id uint64) (locale.ResourceTranslationSet, error) @@ -148,12 +149,27 @@ func (svc resourceTranslationsManager) Locale() locale.Resource { return svc.locale } +func (svc resourceTranslationsManager) Chart(ctx context.Context, namespaceID uint64, id uint64) (locale.ResourceTranslationSet, error) { + var ( + err error + out locale.ResourceTranslationSet + res *types.Chart + ) + + res, err = svc.loadChart(ctx, svc.store, namespaceID, id) + if err != nil { + return nil, err + } + + tmp, err := svc.chartExtended(ctx, res) + return append(out, tmp...), err +} + func (svc resourceTranslationsManager) Module(ctx context.Context, namespaceID uint64, id uint64) (locale.ResourceTranslationSet, error) { var ( err error out locale.ResourceTranslationSet res *types.Module - k types.LocaleKey ) res, err = svc.loadModule(ctx, svc.store, namespaceID, id) @@ -161,6 +177,7 @@ func (svc resourceTranslationsManager) Module(ctx context.Context, namespaceID u return nil, err } + var k types.LocaleKey for _, tag := range svc.locale.Tags() { k = types.LocaleKeyModuleName out = append(out, &locale.ResourceTranslation{ @@ -181,7 +198,6 @@ func (svc resourceTranslationsManager) Namespace(ctx context.Context, id uint64) err error out locale.ResourceTranslationSet res *types.Namespace - k types.LocaleKey ) res, err = svc.loadNamespace(ctx, svc.store, id) @@ -189,6 +205,7 @@ func (svc resourceTranslationsManager) Namespace(ctx context.Context, id uint64) return nil, err } + var k types.LocaleKey for _, tag := range svc.locale.Tags() { k = types.LocaleKeyNamespaceName out = append(out, &locale.ResourceTranslation{ @@ -224,7 +241,6 @@ func (svc resourceTranslationsManager) Page(ctx context.Context, namespaceID uin err error out locale.ResourceTranslationSet res *types.Page - k types.LocaleKey ) res, err = svc.loadPage(ctx, svc.store, namespaceID, id) @@ -232,6 +248,7 @@ func (svc resourceTranslationsManager) Page(ctx context.Context, namespaceID uin return nil, err } + var k types.LocaleKey for _, tag := range svc.locale.Tags() { k = types.LocaleKeyPageTitle out = append(out, &locale.ResourceTranslation{ diff --git a/compose/service/locale.go b/compose/service/locale.go index 360684e18..3942cbfe7 100644 --- a/compose/service/locale.go +++ b/compose/service/locale.go @@ -272,6 +272,45 @@ func (svc resourceTranslationsManager) pageExtended(ctx context.Context, res *ty return } +func (svc resourceTranslationsManager) chartExtended(_ context.Context, res *types.Chart) (out locale.ResourceTranslationSet, err error) { + var ( + yAxisLabelK = types.LocaleKeyChartYAxisLabel + metricLabelK = types.LocaleKeyChartMetricsMetricIDLabel + ) + + for _, report := range res.Config.Reports { + for _, tag := range svc.locale.Tags() { + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: yAxisLabelK.Path, + Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), yAxisLabelK.Path), + }) + } + + for _, metric := range report.Metrics { + if _, ok := metric["metricID"]; ok { + mID, is := metric["metricID"].(string) + if !is { + continue + } + mpl := strings.NewReplacer("{{metricID}}", mID) + + for _, tag := range svc.locale.Tags() { + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: mpl.Replace(metricLabelK.Path), + Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), mpl.Replace(metricLabelK.Path)), + }) + } + } + } + } + + return +} + func (svc resourceTranslationsManager) pageExtendedAutomationBlock(tag language.Tag, res *types.Page, block types.PageBlock, blockID uint64) (locale.ResourceTranslationSet, error) { var ( k = types.LocaleKeyPagePageBlockBlockIDButtonButtonIDLabel diff --git a/compose/types/chart.go b/compose/types/chart.go index 606a5276b..df9019743 100644 --- a/compose/types/chart.go +++ b/compose/types/chart.go @@ -3,6 +3,8 @@ package types import ( "database/sql/driver" "encoding/json" + "github.com/cortezaproject/corteza-server/pkg/locale" + "strings" "time" "github.com/cortezaproject/corteza-server/pkg/filter" @@ -31,6 +33,7 @@ type ( } ChartConfigReport struct { + ReportID uint64 `json:"reportID,string,omitempty"` Filter string `json:"filter"` ModuleID uint64 `json:"moduleID,string,omitempty"` Metrics []map[string]interface{} `json:"metrics,omitempty"` @@ -65,6 +68,76 @@ type ( } ) +func (c Chart) decodeTranslations(tt locale.ResourceTranslationIndex) { + var aux *locale.ResourceTranslation + + for i, report := range c.Config.Reports { + if aux = tt.FindByKey(LocaleKeyChartYAxisLabel.Path); aux != nil { + c.Config.Reports[i].YAxis["label"] = aux.Msg + } + + for j, metric := range report.Metrics { + if metricID, ok := metric["metricID"]; ok { + mID, is := metricID.(string) + if !is { + continue + } + mpl := strings.NewReplacer( + "{{metricID}}", mID, + ) + + if aux = tt.FindByKey(mpl.Replace(LocaleKeyChartMetricsMetricIDLabel.Path)); aux != nil { + c.Config.Reports[i].Metrics[j]["label"] = aux.Msg + } + } + } + } +} + +func (c Chart) encodeTranslations() (out locale.ResourceTranslationSet) { + out = make(locale.ResourceTranslationSet, 0, 12) + + for _, report := range c.Config.Reports { + if mLabel, ok := report.YAxis["label"]; ok { + ml, is := mLabel.(string) + if !is { + continue + } + out = append(out, &locale.ResourceTranslation{ + Resource: c.ResourceTranslation(), + Key: LocaleKeyChartYAxisLabel.Path, + Msg: ml, + }) + } + + for _, metric := range report.Metrics { + if metricID, ok := metric["metricID"]; ok { + mID, is := metricID.(string) + if !is { + continue + } + mpl := strings.NewReplacer( + "{{metricID}}", mID, + ) + + if mLabel, ok := metric["label"]; ok { + ml, is := mLabel.(string) + if !is { + continue + } + out = append(out, &locale.ResourceTranslation{ + Resource: c.ResourceTranslation(), + Key: mpl.Replace(LocaleKeyChartMetricsMetricIDLabel.Path), + Msg: ml, + }) + } + } + } + } + + return +} + // FindByHandle finds chart by it's handle func (set ChartSet) FindByHandle(handle string) *Chart { for i := range set { diff --git a/compose/types/locale.gen.go b/compose/types/locale.gen.go index f50861c39..4a4c8bd35 100644 --- a/compose/types/locale.gen.go +++ b/compose/types/locale.gen.go @@ -23,6 +23,7 @@ type ( // Types and stuff const ( + ChartResourceTranslationType = "compose:chart" ModuleResourceTranslationType = "compose:module" ModuleFieldResourceTranslationType = "compose:module-field" NamespaceResourceTranslationType = "compose:namespace" @@ -31,6 +32,8 @@ const ( var ( // @todo can we remove LocaleKey struct for string constant? + LocaleKeyChartYAxisLabel = LocaleKey{Path: "yAxis.label"} + LocaleKeyChartMetricsMetricIDLabel = LocaleKey{Path: "metrics.{{metricID}}.label"} LocaleKeyModuleName = LocaleKey{Path: "name"} LocaleKeyModuleFieldLabel = LocaleKey{Path: "label"} LocaleKeyModuleFieldMetaDescriptionView = LocaleKey{Path: "meta.description.view"} @@ -57,6 +60,47 @@ var ( LocaleKeyPagePageBlockBlockIDContentBody = LocaleKey{Path: "pageBlock.{{blockID}}.content.body"} ) +// ResourceTranslation returns string representation of Locale resource for Chart by calling ChartResourceTranslation fn +// +// Locale resource is in "compose:chart/..." format +// +// This function is auto-generated +func (r Chart) ResourceTranslation() string { + return ChartResourceTranslation(r.NamespaceID, r.ID) +} + +// ChartResourceTranslation returns string representation of Locale resource for Chart +// +// Locale resource is in the compose:chart/... format +// +// This function is auto-generated +func ChartResourceTranslation(NamespaceID uint64, ID uint64) string { + cpts := []interface{}{ + ChartResourceTranslationType, + strconv.FormatUint(NamespaceID, 10), + strconv.FormatUint(ID, 10), + } + + return fmt.Sprintf(ChartResourceTranslationTpl(), cpts...) +} + +func ChartResourceTranslationTpl() string { + return "%s/%s/%s" +} + +func (r *Chart) DecodeTranslations(tt locale.ResourceTranslationIndex) { + + r.decodeTranslations(tt) +} + +func (r *Chart) EncodeTranslations() (out locale.ResourceTranslationSet) { + out = locale.ResourceTranslationSet{} + + out = append(out, r.encodeTranslations()...) + + return out +} + // ResourceTranslation returns string representation of Locale resource for Module by calling ModuleResourceTranslation fn // // Locale resource is in "compose:module/..." format diff --git a/pkg/envoy/resource/compose_chart.go b/pkg/envoy/resource/compose_chart.go index 6e88a371d..f49c654ff 100644 --- a/pkg/envoy/resource/compose_chart.go +++ b/pkg/envoy/resource/compose_chart.go @@ -65,21 +65,25 @@ func (r *ComposeChart) SysID() uint64 { return r.Res.ID } -func (r *ComposeChart) RBACParts() (resource string, ref *Ref, path []*Ref) { - ref = r.Ref() +func (r *ComposeChart) resourceParts(tpl string) (resource string, ref *Ref, path []*Ref) { + ref = r.Ref().Constraint(r.RefNs) path = []*Ref{r.RefNs} resource = fmt.Sprintf(types.ChartRbacResourceTpl(), types.ChartResourceType, r.RefNs.Identifiers.First(), firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle)) return } -// func (r *ComposeChart) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) { -// ref = r.Ref() -// path = []*Ref{r.RefNs} -// resource = fmt.Sprintf(types.ChartResourceTranslationTpl(), types.ChartResourceTranslationType, r.RefNs.Identifiers.First(), firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle)) +func (r *ComposeChart) RBACParts() (resource string, ref *Ref, path []*Ref) { + return r.resourceParts(types.ChartRbacResourceTpl()) +} -// return -// } +func (r *ComposeChart) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) { + return r.resourceParts(types.ChartResourceTranslationTpl()) +} + +func (r *ComposeChart) encodeTranslations() ([]*ResourceTranslation, error) { + return nil, nil +} // FindComposeChart looks for the chart in the resources func FindComposeChart(rr InterfaceSet, ii Identifiers) (ch *types.Chart) { diff --git a/pkg/envoy/resource/resource_translation.gen.go b/pkg/envoy/resource/resource_translation.gen.go index 50adc24e2..46c8ae415 100644 --- a/pkg/envoy/resource/resource_translation.gen.go +++ b/pkg/envoy/resource/resource_translation.gen.go @@ -10,6 +10,18 @@ import ( systemTypes "github.com/cortezaproject/corteza-server/system/types" ) +func (r *ComposeChart) EncodeTranslations() ([]*ResourceTranslation, error) { + out := make([]*ResourceTranslation, 0, 10) + + rr := r.Res.EncodeTranslations() + rr.SetLanguage(defaultLanguage) + res, ref, pp := r.ResourceTranslationParts() + out = append(out, NewResourceTranslation(systemTypes.FromLocale(rr), res, ref, pp...)) + + tmp, err := r.encodeTranslations() + return append(out, tmp...), err + +} func (r *ComposeModule) EncodeTranslations() ([]*ResourceTranslation, error) { out := make([]*ResourceTranslation, 0, 10) diff --git a/pkg/envoy/resource/resource_translation_parse.gen.go b/pkg/envoy/resource/resource_translation_parse.gen.go index 12bd1f611..c4cbe2b72 100644 --- a/pkg/envoy/resource/resource_translation_parse.gen.go +++ b/pkg/envoy/resource/resource_translation_parse.gen.go @@ -47,6 +47,16 @@ func ParseResourceTranslation(res string) (string, *Ref, []*Ref, error) { // make the resource provide the slice of parent resources we should nest under switch resourceType { + case composeTypes.ChartResourceTranslationType: + if len(path) != 2 { + return "", nil, nil, fmt.Errorf("expecting 2 reference components in path, got %d", len(path)) + } + ref, pp, err := ComposeChartResourceTranslationReferences( + path[0], + path[1], + ) + return composeTypes.ChartResourceTranslationType, ref, pp, err + case composeTypes.ModuleResourceTranslationType: if len(path) != 2 { return "", nil, nil, fmt.Errorf("expecting 2 reference components in path, got %d", len(path)) diff --git a/pkg/envoy/resource/resource_translation_references_compose.gen.go b/pkg/envoy/resource/resource_translation_references_compose.gen.go index 1df89c42e..fc6454e5f 100644 --- a/pkg/envoy/resource/resource_translation_references_compose.gen.go +++ b/pkg/envoy/resource/resource_translation_references_compose.gen.go @@ -10,6 +10,16 @@ import ( "github.com/cortezaproject/corteza-server/compose/types" ) +// ComposeChartResourceTranslationReferences generates Locale references +// +// This function is auto-generated +func ComposeChartResourceTranslationReferences(namespaceID string, self string) (res *Ref, pp []*Ref, err error) { + res = &Ref{ResourceType: types.ChartResourceType, Identifiers: MakeIdentifiers(self)} + pp = append(pp, &Ref{ResourceType: types.NamespaceResourceType, Identifiers: MakeIdentifiers(namespaceID)}) + + return +} + // ComposeModuleResourceTranslationReferences generates Locale references // // This function is auto-generated diff --git a/pkg/envoy/store/resource_translation_marshal.go b/pkg/envoy/store/resource_translation_marshal.go index 4483386a6..119f55c86 100644 --- a/pkg/envoy/store/resource_translation_marshal.go +++ b/pkg/envoy/store/resource_translation_marshal.go @@ -172,20 +172,20 @@ func (n *resourceTranslation) makeResourceTranslation(pl *payload) (string, erro return composeTypes.PageResourceTranslation(p0ID, p1ID), nil - // case composeTypes.ChartResourceType: - // p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.refPathRes[0].Identifiers) - // if p0 == nil { - // return "", resource.ComposeNamespaceErrUnresolved(n.refPathRes[0].Identifiers) - // } - // p0ID = p0.ID + case composeTypes.ChartResourceType: + p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.refPathRes[0].Identifiers) + if p0 == nil { + return "", resource.ComposeNamespaceErrUnresolved(n.refPathRes[0].Identifiers) + } + p0ID = p0.ID - // p1 := resource.FindComposeChart(pl.state.ParentResources, n.refLocaleRes.Identifiers) - // if p1 == nil { - // return "", resource.ComposeChartErrUnresolved(n.refLocaleRes.Identifiers) - // } - // p1ID = p1.ID + p1 := resource.FindComposeChart(pl.state.ParentResources, n.refLocaleRes.Identifiers) + if p1 == nil { + return "", resource.ComposeChartErrUnresolved(n.refLocaleRes.Identifiers) + } + p1ID = p1.ID - // return composeTypes.ChartResourceTranslation(p0ID, p1ID), nil + return composeTypes.ChartResourceTranslation(p0ID, p1ID), nil case composeTypes.ModuleFieldResourceType: p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.refPathRes[0].Identifiers) diff --git a/pkg/envoy/yaml/resource_translation_marshal.go b/pkg/envoy/yaml/resource_translation_marshal.go index 127aa0cd3..e2bf78e13 100644 --- a/pkg/envoy/yaml/resource_translation_marshal.go +++ b/pkg/envoy/yaml/resource_translation_marshal.go @@ -162,24 +162,24 @@ func (r *resourceTranslation) makeResourceTranslationResource(state *envoy.Resou return fmt.Sprintf(composeTypes.ModuleFieldResourceTranslationTpl(), composeTypes.ModuleFieldResourceTranslationType, p0ID, p1ID, p2ID), nil - // case composeTypes.ChartResourceType: - // if len(res.RefPath) > 0 { - // p0 := resource.FindComposeNamespace(state.ParentResources, res.RefPath[0].Identifiers) - // if p0 == nil { - // return "", resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) - // } - // p0ID = p0.Slug - // } + case composeTypes.ChartResourceType: + if len(res.RefPath) > 0 { + p0 := resource.FindComposeNamespace(state.ParentResources, res.RefPath[0].Identifiers) + if p0 == nil { + return "", resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) + } + p0ID = p0.Slug + } - // if res.RefRes != nil { - // p1 := resource.FindComposeChart(state.ParentResources, res.RefRes.Identifiers) - // if p1 == nil { - // return "", resource.ComposeChartErrUnresolved(res.RefRes.Identifiers) - // } - // p1ID = p1.Handle - // } + if res.RefRes != nil { + p1 := resource.FindComposeChart(state.ParentResources, res.RefRes.Identifiers) + if p1 == nil { + return "", resource.ComposeChartErrUnresolved(res.RefRes.Identifiers) + } + p1ID = p1.Handle + } - // return fmt.Sprintf(composeTypes.ChartResourceTranslationTpl(), composeTypes.ChartResourceTranslationType, p0ID, p1ID), nil + return fmt.Sprintf(composeTypes.ChartResourceTranslationTpl(), composeTypes.ChartResourceTranslationType, p0ID, p1ID), nil // case automationTypes.WorkflowResourceType: // if res.RefRes != nil { diff --git a/pkg/provision/migrations_202203_resource_translations.go b/pkg/provision/migrations_202203_resource_translations.go new file mode 100644 index 000000000..fb61acc2b --- /dev/null +++ b/pkg/provision/migrations_202203_resource_translations.go @@ -0,0 +1,116 @@ +package provision + +import ( + "context" + "fmt" + cmpTypes "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/store" + sysTypes "github.com/cortezaproject/corteza-server/system/types" + "go.uber.org/zap" + "strings" +) + +// Migrates resource translations from the resource +// struct to the dedicated store (table) +// +// While doing this, we also modify some resource substructure: +// - chart reports (assign report IDs) +// +// Note: we will migrate all translations to current default language +// If you do not like that, shut down Corteza after migrations and fix this directly in the store +func migratePost202203ResourceTranslations(ctx context.Context, log *zap.Logger, s store.Storer) (err error) { + log.Info("migrating post 202203 resource translations") + + var ( + migrated = make(map[string]bool) + ) + + set, _, err := store.SearchResourceTranslations(ctx, s, sysTypes.ResourceTranslationFilter{}) + if err != nil { + return + } + err = set.Walk(func(r *sysTypes.ResourceTranslation) error { + var pos = strings.Index(r.Resource, "/") + if pos < 0 { + return nil + } + + migrated[r.Resource[0:pos]] = true + return nil + }) + if err != nil { + return + } + + if !migrated[cmpTypes.ChartResourceTranslationType] { + if err = migrateComposeChartResourceTranslations(ctx, log, s); err != nil { + return + } + } + + return +} + +// migrate resource translations for compose chart +func migrateComposeChartResourceTranslations(ctx context.Context, log *zap.Logger, s store.Storer) error { + var ( + tt sysTypes.ResourceTranslationSet + ) + set, _, err := store.SearchComposeCharts(ctx, s, cmpTypes.ChartFilter{Deleted: filter.StateInclusive}) + if err != nil { + return err + } + + log.Info("migrating compose charts", zap.Int("count", len(set))) + + return s.Tx(ctx, func(ctx context.Context, s store.Storer) error { + return set.Walk(func(res *cmpTypes.Chart) (err error) { + if tt, err = convertComposeChartReportsTranslations(res); err != nil { + return err + } + + if err = store.CreateResourceTranslation(ctx, s, tt...); err != nil { + return + } + + if err = store.UpdateComposeChart(ctx, s, res); err != nil { + return + } + + return + }) + }) +} + +// collects translations for compose chart blocks and alters them (adding block ID) +func convertComposeChartReportsTranslations(res *cmpTypes.Chart) (sysTypes.ResourceTranslationSet, error) { + var tt sysTypes.ResourceTranslationSet + + for i, r := range res.Config.Reports { + reportID := id.Next() + res.Config.Reports[i].ReportID = reportID + + if _, ok := r.YAxis["label"]; ok { + tt = append(tt, + makeResourceTranslation(res, "yAxis.label", r.YAxis["label"].(string)), + ) + } + + // Ensure chart report metric IDs + for j, m := range r.Metrics { + metricID := id.Next() + res.Config.Reports[i].Metrics[j]["metricID"] = fmt.Sprintf("%d", metricID) + cmx := fmt.Sprintf("metrics.%d.", metricID) + + if _, ok := m["label"]; ok { + tt = append(tt, + makeResourceTranslation(res, cmx+"label", m["label"].(string)), + ) + } + } + } + + return tt, nil +} diff --git a/pkg/provision/provision.go b/pkg/provision/provision.go index 20363fc09..6868c5b90 100644 --- a/pkg/provision/provision.go +++ b/pkg/provision/provision.go @@ -41,6 +41,9 @@ func Run(ctx context.Context, log *zap.Logger, s store.Storer, provisionOpt opti func() error { return cleanupPre202109Settings(ctx, log.Named("pre-202109-settings"), s) }, func() error { return migrateResourceTranslations(ctx, log.Named("resource-translations"), s) }, func() error { return migrateReportIdentifiers(ctx, log.Named("report-identifiers"), s) }, + func() error { + return migratePost202203ResourceTranslations(ctx, log.Named("post-202203-resource-translations"), s) + }, // Config (full & partial) func() error { return importConfig(ctx, log.Named("config"), s, provisionOpt.Path) },