diff --git a/cmd/server/pactasrv/analysis.go b/cmd/server/pactasrv/analysis.go index d8b8ef1..4316975 100644 --- a/cmd/server/pactasrv/analysis.go +++ b/cmd/server/pactasrv/analysis.go @@ -35,6 +35,9 @@ func (s *Server) ListAnalyses(ctx context.Context, request api.ListAnalysesReque if err := s.populateBlobsInAnalysisArtifacts(ctx, artifacts...); err != nil { return nil, err } + if err := s.populateSnapshotsInAnalyses(ctx, as...); err != nil { + return nil, err + } items, err := dereference(conv.AnalysesToOAPI(as)) if err != nil { return nil, err @@ -76,6 +79,9 @@ func (s *Server) FindAnalysisById(ctx context.Context, request api.FindAnalysisB if err := s.populateBlobsInAnalysisArtifacts(ctx, a.Artifacts...); err != nil { return nil, err } + if err := s.populateSnapshotsInAnalyses(ctx, a); err != nil { + return nil, err + } converted, err := conv.AnalysisToOAPI(a) if err != nil { return nil, err diff --git a/cmd/server/pactasrv/conv/pacta_to_oapi.go b/cmd/server/pactasrv/conv/pacta_to_oapi.go index 62588d1..258d7a4 100644 --- a/cmd/server/pactasrv/conv/pacta_to_oapi.go +++ b/cmd/server/pactasrv/conv/pacta_to_oapi.go @@ -443,6 +443,7 @@ func AnalysisToOAPI(a *pacta.Analysis) (*api.Analysis, error) { FailureCode: fc, FailureMessage: fm, Artifacts: dereferenceAll(aas), + OwnerId: string(a.Owner.ID), }, nil } diff --git a/cmd/server/pactasrv/pactasrv.go b/cmd/server/pactasrv/pactasrv.go index 6e7eeed..b50ebe7 100644 --- a/cmd/server/pactasrv/pactasrv.go +++ b/cmd/server/pactasrv/pactasrv.go @@ -99,6 +99,7 @@ type DB interface { CreateSnapshotOfPortfolio(tx db.Tx, pID pacta.PortfolioID) (pacta.PortfolioSnapshotID, error) CreateSnapshotOfPortfolioGroup(tx db.Tx, pgID pacta.PortfolioGroupID) (pacta.PortfolioSnapshotID, error) CreateSnapshotOfInitiative(tx db.Tx, iID pacta.InitiativeID) (pacta.PortfolioSnapshotID, error) + PortfolioSnapshots(tx db.Tx, ids []pacta.PortfolioSnapshotID) (map[pacta.PortfolioSnapshotID]*pacta.PortfolioSnapshot, error) GetOwnerForUser(tx db.Tx, uID pacta.UserID) (pacta.OwnerID, error) diff --git a/cmd/server/pactasrv/populate.go b/cmd/server/pactasrv/populate.go index 3d9d68a..1d77903 100644 --- a/cmd/server/pactasrv/populate.go +++ b/cmd/server/pactasrv/populate.go @@ -101,6 +101,25 @@ func (s *Server) populateArtifactsInAnalyses( return nil } +func (s *Server) populateSnapshotsInAnalyses( + ctx context.Context, + ts ...*pacta.Analysis, +) error { + getFn := func(a *pacta.Analysis) ([]*pacta.PortfolioSnapshot, error) { + return []*pacta.PortfolioSnapshot{a.PortfolioSnapshot}, nil + } + lookupFn := func(ids []pacta.PortfolioSnapshotID) (map[pacta.PortfolioSnapshotID]*pacta.PortfolioSnapshot, error) { + return s.DB.PortfolioSnapshots(s.DB.NoTxn(ctx), ids) + } + getIDFn := func(a *pacta.PortfolioSnapshot) pacta.PortfolioSnapshotID { + return a.ID + } + if err := populateAll(ts, getFn, getIDFn, lookupFn); err != nil { + return oapierr.Internal("populating portfolio snapshots in analysis failed", zap.Error(err)) + } + return nil +} + func (s *Server) populateBlobsInPortfolios( ctx context.Context, ps ...*pacta.Portfolio, diff --git a/cmd/server/pactasrv/user.go b/cmd/server/pactasrv/user.go index 40cebfd..459eb86 100644 --- a/cmd/server/pactasrv/user.go +++ b/cmd/server/pactasrv/user.go @@ -92,11 +92,19 @@ func (s *Server) FindUserByMe(ctx context.Context, request api.FindUserByMeReque if err != nil { return nil, oapierr.Internal("failed to retrieve user", zap.Error(err)) } - result, err := conv.UserToOAPI(user) + ownerID, err := s.DB.GetOwnerForUser(s.DB.NoTxn(ctx), meID) + if err != nil { + return nil, oapierr.Internal("failed to retrieve owner for user", zap.Error(err)) + } + apiUser, err := conv.UserToOAPI(user) if err != nil { return nil, err } - return api.FindUserByMe200JSONResponse(*result), nil + result := api.FindUserByMe200JSONResponse{ + User: apiUser, + OwnerId: ptr(string(ownerID)), + } + return result, nil } // a callback after login to create or return the user diff --git a/frontend/components/LinkButton.vue b/frontend/components/LinkButton.vue index e6ba870..a64a9b9 100644 --- a/frontend/components/LinkButton.vue +++ b/frontend/components/LinkButton.vue @@ -34,6 +34,7 @@ interface Props { loadingIcon?: string activeClass?: string inactiveClass?: string + external?: boolean } const props = withDefaults(defineProps(), { to: undefined, @@ -49,6 +50,7 @@ const props = withDefaults(defineProps(), { loadingIcon: 'pi pi-spinner pi-spin', activeClass: '', inactiveClass: '', + external: undefined, }) const attrs = useAttrs() @@ -156,6 +158,7 @@ const href = computed(() => { :target="target" :to="to" :aria-disabled="disabled" + :external="props.external" custom > +import { type Analysis, type AccessBlobContentReqItem, type AccessBlobContentResp } from '@/openapi/generated/pacta' +import JSZip from 'jszip' + +const { t } = useI18n() +const { public: { apiServerURL } } = useRuntimeConfig() +const pactaClient = usePACTA() +const { getMaybeMe } = useSession() +const { isAdmin, isSuperAdmin, maybeMeOwnerId } = await getMaybeMe() + +interface Props { + analysis: Analysis +} +const props = defineProps() + +const prefix = 'components/analysis/AccessButtons' +const statePrefix = `${prefix}[${useStateIDGenerator().id()}]` +const tt = (key: string) => t(`${prefix}.${key}`) + +const canAccessAsPublic = computed(() => props.analysis.artifacts.every((asset) => asset.sharedToPublic)) +const canAccessAsAdmin = computed(() => { + if (isAdmin.value || isSuperAdmin.value) { + return props.analysis.artifacts.every((asset) => asset.adminDebugEnabled) + } + return false +}) +const canAccessAsOwner = computed(() => { + if (maybeMeOwnerId.value) { + return maybeMeOwnerId.value === props.analysis.ownerId + } + return false +}) +const canAccess = computed(() => { + return canAccessAsPublic.value || canAccessAsAdmin.value || canAccessAsOwner.value +}) +const downloadInProgress = useState(`${statePrefix}.downloadInProgress`, () => false) +const doDownload = async () => { + downloadInProgress.value = true + const response: AccessBlobContentResp = await pactaClient.accessBlobContent({ + items: props.analysis.artifacts.map((asset): AccessBlobContentReqItem => ({ + blobId: asset.blob.id, + })), + }) + const zip = new JSZip() + await Promise.all(response.items.map( + async (item): Promise => { + const response = await fetch(item.downloadUrl) + const data = await response.blob() + const blob = presentOrFileBug(props.analysis.artifacts.find((artifact) => artifact.blob.id === item.blobId)).blob + const fileName = `${blob.fileName}` + zip.file(fileName, data) + }), + ) + const content = await zip.generateAsync({ type: 'blob' }) + const element = document.createElement('a') + element.href = URL.createObjectURL(content) + const fileName = `${props.analysis.name}.zip` + element.download = fileName + document.body.appendChild(element) + element.click() + document.body.removeChild(element) + downloadInProgress.value = false +} + +const openReport = () => navigateTo(`${apiServerURL}/report/${props.analysis.id}/`, { + open: { + target: '_blank', + }, + external: true, +}) + + + diff --git a/frontend/components/analysis/ListView.vue b/frontend/components/analysis/ListView.vue index d4c7fd3..227375b 100644 --- a/frontend/components/analysis/ListView.vue +++ b/frontend/components/analysis/ListView.vue @@ -3,10 +3,7 @@ import { analysisEditor } from '@/lib/editor' import { type Portfolio, type PortfolioGroup, type Initiative, type Analysis } from '@/openapi/generated/pacta' import { selectedCountSuffix } from '@/lib/selection' -const { public: { apiServerURL } } = useRuntimeConfig() -const { - humanReadableTimeFromStandardString, -} = useTime() +const { humanReadableTimeFromStandardString } = useTime() const pactaClient = usePACTA() const { loading: { withLoading } } = useModal() const i18n = useI18n() @@ -121,12 +118,8 @@ const deleteSelected = () => Promise.all([selectedRows.value.map((row) => delete :header="tt('View')" > diff --git a/frontend/components/analysis/RunButton.vue b/frontend/components/analysis/RunButton.vue new file mode 100644 index 0000000..384fb9b --- /dev/null +++ b/frontend/components/analysis/RunButton.vue @@ -0,0 +1,126 @@ + + + diff --git a/frontend/components/portfolio/ListView.vue b/frontend/components/portfolio/ListView.vue index b611e3c..0a88347 100644 --- a/frontend/components/portfolio/ListView.vue +++ b/frontend/components/portfolio/ListView.vue @@ -39,6 +39,7 @@ const selectedPortfolioIDs = computed({ interface EditorObject extends ReturnType { id: string + analyses: Analysis[] } const prefix = 'components/portfolio/ListView' @@ -56,7 +57,11 @@ const selectedRows = computed({ }, }) -const editorObjects = computed(() => props.portfolios.map((item) => ({ ...portfolioEditor(item, i18n), id: item.id }))) +const editorObjects = computed(() => props.portfolios.map((item) => ({ + id: item.id, + ...portfolioEditor(item, i18n), + analyses: props.analyses.filter((a) => a.portfolioSnapshot.portfolio?.id === item.id), +}))) const selectedPortfolios = computed(() => selectedRows.value.map((row) => row.currentValue.value)) @@ -69,21 +74,6 @@ const saveChanges = (id: string) => { ) } -const runAnalysis = (id: string, analysisType: AnalysisType) => { - const n = props.portfolios.find((p) => p.id === id)?.name ?? 'unknown' - return withLoading( - () => pactaClient.runAnalysis({ - analysisType, - name: `${tt(analysisType)}: ${n}`, - description: `${tt(analysisType)} run at ${new Date().toLocaleString()} for portfolio ${n} (${id})`, - portfolioId: id, - }), - `${prefix}.runPortfolioAnalysis`, - ) -} -const runAudit = (id: string) => runAnalysis(id, AnalysisType.ANALYSIS_TYPE_AUDIT) -const runReport = (id: string) => runAnalysis(id, AnalysisType.ANALYSIS_TYPE_REPORT) - const deletePortfolio = (id: string) => withLoading( () => pactaClient.deletePortfolio(id), `${prefix}.deletePortfolio`, @@ -197,16 +187,11 @@ const deleteSelected = () => Promise.all([selectedRows.value.map((row) => delete

{{ tt('Metadata') }}

-
-
- {{ tt('Created At') }} - {{ humanReadableTimeFromStandardString(slotProps.data.currentValue.value.createdAt).value }} -
-
- {{ tt('Number of Rows') }} - {{ slotProps.data.currentValue.value.numberOfRows }} -
-
+

{{ tt('Memberships') }}

@@ -224,19 +209,73 @@ const deleteSelected = () => Promise.all([selectedRows.value.map((row) => delete />

- {{ tt('Analyses') }} + {{ tt('Analysis') }}

- - TODO - filter analyses by those that match this portfolio - - - + + + + + + + + + + + + + + + + +
+ + +

{{ tt('Editable Properties') }}

diff --git a/frontend/components/portfolio/group/ListView.vue b/frontend/components/portfolio/group/ListView.vue index f539501..f2db465 100644 --- a/frontend/components/portfolio/group/ListView.vue +++ b/frontend/components/portfolio/group/ListView.vue @@ -149,12 +149,11 @@ const editorObjectToIds = (editorObject: EditorObject): string[] => {

Metadata

-
-
- Created At - {{ humanReadableTimeFromStandardString(slotProps.data.currentValue.value.createdAt).value }} -
-
+

Editable Properties

diff --git a/frontend/composables/useSession.ts b/frontend/composables/useSession.ts index 5d5ed5e..9a0ea5e 100644 --- a/frontend/composables/useSession.ts +++ b/frontend/composables/useSession.ts @@ -6,6 +6,7 @@ export const useSession = () => { const prefix = 'useSession' const currentUser = useState(`${prefix}.currentUser`, () => undefined) + const currentOwnerId = useState(`${prefix}.currentOwnerId`, () => undefined) const isAdmin = computed(() => !!currentUser.value && currentUser.value.admin) const isSuperAdmin = computed(() => !!currentUser.value && currentUser.value.superAdmin) @@ -27,8 +28,9 @@ export const useSession = () => { return new Promise((resolve) => { resolvers.value.push(resolve) void pactaClient.findUserByMe() - .then(m => { - currentUser.value = m + .then(resp => { + currentUser.value = resp.user + currentOwnerId.value = resp.ownerId // Let everyone else know we've loaded the user and clear the queue. resolvers.value.forEach((fn) => { fn() }) @@ -53,6 +55,7 @@ export const useSession = () => { return { // Will be a Ref with a value of undefined if the user isn't logged in. maybeMe: currentUser, + maybeMeOwnerId: currentOwnerId, isAdmin, isSuperAdmin, } diff --git a/frontend/lang/en.json b/frontend/lang/en.json index 75e0230..47aebc6 100644 --- a/frontend/lang/en.json +++ b/frontend/lang/en.json @@ -160,7 +160,25 @@ "No Uploaded Portfolios Message": "It looks like you haven't uploaded any portfolios yet - the first step to using the PACTA tool is uploading your portfolio to the platform, where it can be processed, parsed, and validated. Click the button below to upload a new portfolio.", "Number of Rows": "Number of Rows", "Save Changes": "Save Changes", - "Refresh": "Refresh" + "Refresh": "Refresh", + "Run Audit": "Run Audit", + "Run Report": "Run Report", + "AnalysisTypeREPORT": "Report", + "AnalysisTypeAUDIT": "Audit", + "Type": "Type", + "Access": "Access", + "Analysis": "Analysis", + "No Analyses Message": "It looks like you haven't run any analyses yet - the next step to using the PACTA tool is running an analysis on your portfolio. Click the button below to run an audit or a report.", + }, + "components/analysis/AccessButtons": { + "View": "View", + "Download": "Download", + "Denied": "You do not have access to this analysis" + }, + "components/analysis/RunButton": { + "Run": "Run", + "AnalysisTypeAUDIT": "Audit", + "AnalysisTypeREPORT": "Report" }, "components/portfolio/group/ListView": { "Created At": "Created At", @@ -367,7 +385,7 @@ "ErrDuplicate": "This file may be a duplicate, consider removing it.", "ErrTooLarge": "File is too large (100MB max)", "Optional Portfolio Properties": "Optional Portfolio Properties", - "No Edit Properties": "The portfolios have already been created with the settings you selected. You can change their settings by editing each portfolio you created individually." + "No Edit Properties": "The portfolios have been created with the settings you selected. You can change their settings by editing each portfolio you created individually." }, "pages/audit-logs": { "Action": "Action", diff --git a/frontend/lib/editor/analysis.ts b/frontend/lib/editor/analysis.ts index 8fea745..e638365 100644 --- a/frontend/lib/editor/analysis.ts +++ b/frontend/lib/editor/analysis.ts @@ -59,6 +59,10 @@ const createEditorAnalysisFields = (translation: Translation): EditorAnalysisFie name: 'artifacts', label: tt('Artifacts'), }, + ownerId: { + name: 'ownerId', + label: tt('Owner ID'), + }, } } diff --git a/frontend/openapi/generated/pacta/index.ts b/frontend/openapi/generated/pacta/index.ts index e84eeac..ef7d7e1 100644 --- a/frontend/openapi/generated/pacta/index.ts +++ b/frontend/openapi/generated/pacta/index.ts @@ -35,6 +35,7 @@ export type { CompletePortfolioUploadResp } from './models/CompletePortfolioUplo export type { Error } from './models/Error'; export { FailureCode } from './models/FailureCode'; export { FileType } from './models/FileType'; +export type { FindUserByMeResp } from './models/FindUserByMeResp'; export type { HoldingsDate } from './models/HoldingsDate'; export type { IncompleteUpload } from './models/IncompleteUpload'; export type { IncompleteUploadChanges } from './models/IncompleteUploadChanges'; diff --git a/frontend/openapi/generated/pacta/models/AccessBlobContentReqItem.ts b/frontend/openapi/generated/pacta/models/AccessBlobContentReqItem.ts index f7dbbf7..a505880 100644 --- a/frontend/openapi/generated/pacta/models/AccessBlobContentReqItem.ts +++ b/frontend/openapi/generated/pacta/models/AccessBlobContentReqItem.ts @@ -7,6 +7,6 @@ export type AccessBlobContentReqItem = { /** * The id of the blob to request the content for. */ - blob_id: string; + blobId: string; }; diff --git a/frontend/openapi/generated/pacta/models/AccessBlobContentRespItem.ts b/frontend/openapi/generated/pacta/models/AccessBlobContentRespItem.ts index f13e2bd..94af842 100644 --- a/frontend/openapi/generated/pacta/models/AccessBlobContentRespItem.ts +++ b/frontend/openapi/generated/pacta/models/AccessBlobContentRespItem.ts @@ -7,14 +7,14 @@ export type AccessBlobContentRespItem = { /** * The id of the blob to that the content is for. */ - blob_id: string; + blobId: string; /** * The signed URL where the file can be downloaded from, using GET semantics. */ - download_url: string; + downloadUrl: string; /** * The time at which the signed URL will expire. */ - expiration_time: string; + expirationTime: string; }; diff --git a/frontend/openapi/generated/pacta/models/Analysis.ts b/frontend/openapi/generated/pacta/models/Analysis.ts index 3f4ff4c..8735b30 100644 --- a/frontend/openapi/generated/pacta/models/Analysis.ts +++ b/frontend/openapi/generated/pacta/models/Analysis.ts @@ -57,5 +57,9 @@ export type Analysis = { * The list of artifacts that were generated by this analysis */ artifacts: Array; + /** + * the id of the owner of the analysis, if the user has access to see it + */ + ownerId: string; }; diff --git a/frontend/openapi/generated/pacta/models/FileType.ts b/frontend/openapi/generated/pacta/models/FileType.ts index b4358c3..d1da63f 100644 --- a/frontend/openapi/generated/pacta/models/FileType.ts +++ b/frontend/openapi/generated/pacta/models/FileType.ts @@ -9,4 +9,9 @@ export enum FileType { FILE_TYPE_ZIP = 'FileTypeZIP', FILE_TYPE_JSON = 'FileTypeJSON', FILE_TYPE_HTML = 'FileTypeHTML', + FILE_TYPE_TEXT = 'FileTypeTEXT', + FILE_TYPE_CSS = 'FileTypeCSS', + FILE_TYPE_JS = 'FileTypeJS', + FILE_TYPE_TTF = 'FileTypeTTF', + FILE_TYPE_UNKNOWN = 'FileTypeUNKNOWN', } diff --git a/frontend/openapi/generated/pacta/models/FindUserByMeResp.ts b/frontend/openapi/generated/pacta/models/FindUserByMeResp.ts new file mode 100644 index 0000000..f8337f8 --- /dev/null +++ b/frontend/openapi/generated/pacta/models/FindUserByMeResp.ts @@ -0,0 +1,15 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { User } from './User'; + +export type FindUserByMeResp = { + user?: User; + /** + * the id of the owner of the user, if logged in + */ + ownerId?: string; +}; + diff --git a/frontend/openapi/generated/pacta/services/DefaultService.ts b/frontend/openapi/generated/pacta/services/DefaultService.ts index 3b4815b..35e8ea7 100644 --- a/frontend/openapi/generated/pacta/services/DefaultService.ts +++ b/frontend/openapi/generated/pacta/services/DefaultService.ts @@ -11,6 +11,7 @@ import type { AuditLogQueryReq } from '../models/AuditLogQueryReq'; import type { AuditLogQueryResp } from '../models/AuditLogQueryResp'; import type { CompletePortfolioUploadReq } from '../models/CompletePortfolioUploadReq'; import type { CompletePortfolioUploadResp } from '../models/CompletePortfolioUploadResp'; +import type { FindUserByMeResp } from '../models/FindUserByMeResp'; import type { IncompleteUpload } from '../models/IncompleteUpload'; import type { IncompleteUploadChanges } from '../models/IncompleteUploadChanges'; import type { Initiative } from '../models/Initiative'; @@ -799,10 +800,10 @@ export class DefaultService { /** * gets info about the logged in user * Returns the logged in user, if the user is logged in, otherwise returns empty - * @returns User user response + * @returns FindUserByMeResp user response * @throws ApiError */ - public findUserByMe(): CancelablePromise { + public findUserByMe(): CancelablePromise { return this.httpRequest.request({ method: 'GET', url: '/user/me', diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bec86ab..e38fa00 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "dependencies": { "@azure/msal-browser": "^3.2.0", "axios": "^1.5.1", + "jszip": "^3.10.1", "primeflex": "^3.3.1", "primeicons": "^6.0.1", "primevue": "^3.32.0", @@ -6724,8 +6725,7 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/cosmiconfig": { "version": "8.2.0", @@ -10093,6 +10093,11 @@ "node": ">=10.18.0" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/immutable": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.2.tgz", @@ -10164,8 +10169,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -10851,8 +10855,7 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/isexe": { "version": "2.0.0", @@ -11085,6 +11088,44 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -11248,6 +11289,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -13471,6 +13520,11 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -14511,8 +14565,7 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/promise-inflight": { "version": "1.0.1", @@ -15694,6 +15747,11 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -17988,8 +18046,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { "version": "9.0.0", @@ -23627,8 +23684,7 @@ "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "cosmiconfig": { "version": "8.2.0", @@ -26151,6 +26207,11 @@ "integrity": "sha512-+oXiHwOEPr1IE5zY0tcBLED/CYcre15J4nwL50x3o0jxWqEkyjrusiKP3YSU+tr9fvJp33ZcP5Gpj2295g3aEw==", "dev": true }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "immutable": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.2.tgz", @@ -26206,8 +26267,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -26683,8 +26743,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "isexe": { "version": "2.0.0", @@ -26868,6 +26927,46 @@ } } }, + "jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -27014,6 +27113,14 @@ "type-check": "~0.4.0" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -28741,6 +28848,11 @@ "tar": "^6.1.11" } }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -29451,8 +29563,7 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "promise-inflight": { "version": "1.0.1", @@ -30343,6 +30454,11 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -32028,8 +32144,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "uuid": { "version": "9.0.0", diff --git a/frontend/package.json b/frontend/package.json index 53b6056..cdefa09 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -41,6 +41,7 @@ "dependencies": { "@azure/msal-browser": "^3.2.0", "axios": "^1.5.1", + "jszip": "^3.10.1", "primeflex": "^3.3.1", "primeicons": "^6.0.1", "primevue": "^3.32.0", diff --git a/openapi/pacta.yaml b/openapi/pacta.yaml index e637efe..f2275a1 100644 --- a/openapi/pacta.yaml +++ b/openapi/pacta.yaml @@ -713,7 +713,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/User' + $ref: '#/components/schemas/FindUserByMeResp' /user/authentication-followup: post: description: Creates a user in the database, if the user does not yet exist, or returns the existing user. @@ -1575,9 +1575,9 @@ components: AccessBlobContentReqItem: type: object required: - - blob_id + - blobId properties: - blob_id: + blobId: type: string description: The id of the blob to request the content for. AccessBlobContentResp: @@ -1593,17 +1593,17 @@ components: AccessBlobContentRespItem: type: object required: - - blob_id - - download_url - - expiration_time + - blobId + - downloadUrl + - expirationTime properties: - blob_id: + blobId: type: string description: The id of the blob to that the content is for. - download_url: + downloadUrl: type: string description: The signed URL where the file can be downloaded from, using GET semantics. - expiration_time: + expirationTime: format: date-time type: string description: The time at which the signed URL will expire. @@ -1844,6 +1844,7 @@ components: - description - createdAt - artifacts + - ownerId properties: id: type: string @@ -1886,6 +1887,9 @@ components: description: The list of artifacts that were generated by this analysis items: $ref: '#/components/schemas/AnalysisArtifact' + ownerId: + type: string + description: the id of the owner of the analysis, if the user has access to see it AnalysisChanges: type: object properties: @@ -1992,6 +1996,14 @@ components: task_id: type: string description: The ID of the async task for processing the portfoio + FindUserByMeResp: + type: object + properties: + user: + $ref: '#/components/schemas/User' + ownerId: + type: string + description: the id of the owner of the user, if logged in AuditLogAction: type: string enum: