From 4fc2c6b2e3203e45fd71c94055ab3e1e03b684dd Mon Sep 17 00:00:00 2001 From: Grady Berry Ward Date: Wed, 24 Jan 2024 16:47:13 -0700 Subject: [PATCH] Adds Missing Line Item Initiative Management Features (#175) --- cmd/server/pactasrv/conv/pacta_to_oapi.go | 15 +- cmd/server/pactasrv/initiative.go | 5 +- cmd/server/pactasrv/populate.go | 23 +++ db/sqldb/portfolio_initiative.go | 2 +- frontend/app.vue | 13 +- .../components/portfolio/DownloadButton.vue | 48 ++++++ frontend/composables/useModal.ts | 10 ++ frontend/lang/en.json | 13 ++ frontend/lib/editor/portfolio.ts | 4 + .../generated/pacta/models/Portfolio.ts | 5 + frontend/pages/initiative/[id]/portfolios.vue | 146 ++++++++++++++++++ openapi/pacta.yaml | 3 + 12 files changed, 270 insertions(+), 17 deletions(-) create mode 100644 frontend/components/portfolio/DownloadButton.vue create mode 100644 frontend/pages/initiative/[id]/portfolios.vue diff --git a/cmd/server/pactasrv/conv/pacta_to_oapi.go b/cmd/server/pactasrv/conv/pacta_to_oapi.go index 7ea15fa..8a80a6f 100644 --- a/cmd/server/pactasrv/conv/pacta_to_oapi.go +++ b/cmd/server/pactasrv/conv/pacta_to_oapi.go @@ -69,10 +69,10 @@ func InitiativeToOAPI(i *pacta.Initiative) (*api.Initiative, error) { func portfolioInitiativeMembershipToOAPIPortfolio(in *pacta.PortfolioInitiativeMembership) (api.PortfolioInitiativeMembershipPortfolio, error) { var zero api.PortfolioInitiativeMembershipPortfolio - out := &api.PortfolioInitiativeMembershipPortfolio{ + out := api.PortfolioInitiativeMembershipPortfolio{ CreatedAt: in.CreatedAt, } - if in.AddedBy != nil && in.AddedBy.ID == "" { + if in.AddedBy != nil && in.AddedBy.ID != "" { out.AddedByUserId = strPtr(in.AddedBy.ID) } p, err := PortfolioToOAPI(in.Portfolio) @@ -80,7 +80,7 @@ func portfolioInitiativeMembershipToOAPIPortfolio(in *pacta.PortfolioInitiativeM return zero, oapierr.Internal("portfolioInitiativeMembershipToOAPI: portfolioToOAPI failed", zap.Error(err)) } out.Portfolio = *p - return zero, nil + return out, nil } func portfolioInitiativeMembershipToOAPIInitiative(in *pacta.PortfolioInitiativeMembership) (api.PortfolioInitiativeMembershipInitiative, error) { @@ -253,6 +253,14 @@ func PortfolioToOAPI(p *pacta.Portfolio) (*api.Portfolio, error) { PortfolioGroup: *pg, }) } + var blob *api.Blob + if p.Blob != nil { + b, err := BlobToOAPI(p.Blob) + if err != nil { + return nil, oapierr.Internal("portfolioToOAPI: blobToOAPI failed", zap.Error(err)) + } + blob = b + } pims, err := convAll(p.PortfolioInitiativeMemberships, portfolioInitiativeMembershipToOAPIInitiative) if err != nil { return nil, oapierr.Internal("portfolioToOAPI: portfolioInitiativeMembershipToOAPIInitiative failed", zap.Error(err)) @@ -264,6 +272,7 @@ func PortfolioToOAPI(p *pacta.Portfolio) (*api.Portfolio, error) { return &api.Portfolio{ Id: string(p.ID), Name: p.Name, + Blob: blob, Description: p.Description, CreatedAt: p.CreatedAt, NumberOfRows: p.NumberOfRows, diff --git a/cmd/server/pactasrv/initiative.go b/cmd/server/pactasrv/initiative.go index 4f65c1b..d558da2 100644 --- a/cmd/server/pactasrv/initiative.go +++ b/cmd/server/pactasrv/initiative.go @@ -152,6 +152,9 @@ func (s *Server) FindInitiativeById(ctx context.Context, request api.FindInitiat return nil, oapierr.Internal("failed to load portfolios for initiative", zap.String("initiative_id", string(i.ID)), zap.Error(err)) } i.PortfolioInitiativeMemberships = portfolios + if err := s.populatePortfoliosInInitiatives(ctx, []*pacta.Initiative{i}); err != nil { + return nil, err + } relationships, err := s.DB.InitiativeUserRelationshipsByInitiative(s.DB.NoTxn(ctx), i.ID) if err != nil { return nil, oapierr.Internal("failed to load initiative user relationships for initiative", zap.String("initiative_id", string(i.ID)), zap.Error(err)) @@ -296,7 +299,7 @@ func (s *Server) initiativeDoAuthzAndAuditLog(ctx context.Context, iID pacta.Ini } else { as.authorizedAsActorType = ptr(pacta.AuditLogActorType_Public) } - case pacta.AuditLogAction_Delete, pacta.AuditLogAction_Create, pacta.AuditLogAction_Update: + case pacta.AuditLogAction_Delete, pacta.AuditLogAction_Create, pacta.AuditLogAction_Update, pacta.AuditLogAction_Download: if userIsInitiativeManager { as.authorizedAsActorType = ptr(pacta.AuditLogActorType_Owner) as.isAuthorized = true diff --git a/cmd/server/pactasrv/populate.go b/cmd/server/pactasrv/populate.go index 1d77903..b6f52e1 100644 --- a/cmd/server/pactasrv/populate.go +++ b/cmd/server/pactasrv/populate.go @@ -32,6 +32,29 @@ func (s *Server) populatePortfoliosInPortfolioGroups( return nil } +func (s *Server) populatePortfoliosInInitiatives( + ctx context.Context, + ts []*pacta.Initiative, +) error { + getFn := func(i *pacta.Initiative) ([]*pacta.Portfolio, error) { + result := []*pacta.Portfolio{} + for _, member := range i.PortfolioInitiativeMemberships { + result = append(result, member.Portfolio) + } + return result, nil + } + lookupFn := func(ids []pacta.PortfolioID) (map[pacta.PortfolioID]*pacta.Portfolio, error) { + return s.DB.Portfolios(s.DB.NoTxn(ctx), ids) + } + getIDFn := func(p *pacta.Portfolio) pacta.PortfolioID { + return p.ID + } + if err := populateAll(ts, getFn, getIDFn, lookupFn); err != nil { + return oapierr.Internal("populating portfolios in initiatives failed", zap.Error(err)) + } + return nil +} + func (s *Server) populateInitiativesInPortfolios( ctx context.Context, is []*pacta.Portfolio, diff --git a/db/sqldb/portfolio_initiative.go b/db/sqldb/portfolio_initiative.go index a59e327..335840c 100644 --- a/db/sqldb/portfolio_initiative.go +++ b/db/sqldb/portfolio_initiative.go @@ -95,7 +95,7 @@ func rowToPortfolioInitiativeMembership(row rowScanner) (*pacta.PortfolioInitiat } func rowsToPortfolioInitiativeMemberships(rows pgx.Rows) ([]*pacta.PortfolioInitiativeMembership, error) { - return mapRows("portfolio_initiaitve_membership", rows, rowToPortfolioInitiativeMembership) + return mapRows("portfolio_initiative_membership", rows, rowToPortfolioInitiativeMembership) } func validatePortfolioInitiativeMembershipForCreate(pim *pacta.PortfolioInitiativeMembership) error { diff --git a/frontend/app.vue b/frontend/app.vue index 8c6682f..26eb0da 100644 --- a/frontend/app.vue +++ b/frontend/app.vue @@ -1,16 +1,5 @@ + + diff --git a/frontend/composables/useModal.ts b/frontend/composables/useModal.ts index 112423d..ae62c2d 100644 --- a/frontend/composables/useModal.ts +++ b/frontend/composables/useModal.ts @@ -1,5 +1,6 @@ import { type Ref } from 'vue' import { type ErrorObject } from 'serialize-error' +import { serializeError } from 'serialize-error' export const useModal = () => { const prefix = 'useModal' @@ -24,6 +25,14 @@ export const useModal = () => { // error const errorModalVisible = newModalVisibilityState('errorModalVisible') const error = useState('errorModal.error') + const handleError = (err: Error) => { + if (process.client) { + console.log(err) + } + error.value = serializeError(err) + errorModalVisible.value = true + clearLoading() + } // loading const loadingSet = useState>(`${prefix}.loadingSet`, () => new Set()) @@ -86,6 +95,7 @@ export const useModal = () => { error: { error, errorModalVisible, + handleError, }, permissionDenied: { permissionDeniedVisible, diff --git a/frontend/lang/en.json b/frontend/lang/en.json index 4d491a5..0f72367 100644 --- a/frontend/lang/en.json +++ b/frontend/lang/en.json @@ -346,6 +346,17 @@ "this user": "this user's public activities on the platform", "you": "your public activities on the platform" }, + "pages/initiative/id/portfolios": { + "Added By User": "Added by User", + "Added At": "Added At", + "Details": "Details", + "View User": "View User", + "Portfolio":"Portfolio", + "Remove": "Remove", + "Refresh": "Refresh", + "Download": "Download", + "Download All": "Download All" + }, "components/user/Toolbar": { "Edit": "Edit", "Profile": "Profile" @@ -448,6 +459,8 @@ "pages/initiative/relationships": { "Actions": "Actions", "Make Manager": "Make Manager", + "Remove Manager": "Remove Manager", + "Manager": "Manager", "Member": "Member", "Remove From Initiative": "Remove From Initiative", "Role": "Role", diff --git a/frontend/lib/editor/portfolio.ts b/frontend/lib/editor/portfolio.ts index ecbcd7c..ee7c0e9 100644 --- a/frontend/lib/editor/portfolio.ts +++ b/frontend/lib/editor/portfolio.ts @@ -64,6 +64,10 @@ const createEditorPortfolioFields = (translation: Translation): EditorPortfolioF name: 'initiatives', label: tt('Initiatives'), }, + blob: { + name: 'blob', + label: tt('Blob'), + }, } } diff --git a/frontend/openapi/generated/pacta/models/Portfolio.ts b/frontend/openapi/generated/pacta/models/Portfolio.ts index 2240c63..002e3cc 100644 --- a/frontend/openapi/generated/pacta/models/Portfolio.ts +++ b/frontend/openapi/generated/pacta/models/Portfolio.ts @@ -3,6 +3,7 @@ /* tslint:disable */ /* eslint-disable */ +import type { Blob } from './Blob'; import type { HoldingsDate } from './HoldingsDate'; import type { OptionalBoolean } from './OptionalBoolean'; import type { PortfolioGroupMembershipPortfolioGroup } from './PortfolioGroupMembershipPortfolioGroup'; @@ -25,6 +26,10 @@ export type Portfolio = { * The time at which this portfolio was successfully parsed from a raw */ createdAt: string; + /** + * Information about the logical portfolio file itself + */ + blob?: Blob; /** * Whether the admin debug mode is enabled for this portfolio */ diff --git a/frontend/pages/initiative/[id]/portfolios.vue b/frontend/pages/initiative/[id]/portfolios.vue new file mode 100644 index 0000000..6c53f34 --- /dev/null +++ b/frontend/pages/initiative/[id]/portfolios.vue @@ -0,0 +1,146 @@ + + + diff --git a/openapi/pacta.yaml b/openapi/pacta.yaml index 2a3757b..3aabe36 100644 --- a/openapi/pacta.yaml +++ b/openapi/pacta.yaml @@ -1692,6 +1692,9 @@ components: type: string format: date-time description: The time at which this portfolio was successfully parsed from a raw + blob: + description: Information about the logical portfolio file itself + $ref: '#/components/schemas/Blob' adminDebugEnabled: type: boolean description: Whether the admin debug mode is enabled for this portfolio