Skip to content

Commit

Permalink
Barebones Analysis Display + Audit Logs
Browse files Browse the repository at this point in the history
  • Loading branch information
gbdubs committed Jan 17, 2024
1 parent 50b5350 commit aa9899e
Show file tree
Hide file tree
Showing 15 changed files with 669 additions and 54 deletions.
10 changes: 10 additions & 0 deletions cmd/server/pactasrv/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ func (s *Server) ListAnalyses(ctx context.Context, request api.ListAnalysesReque
if err != nil {
return nil, oapierr.Internal("failed to query analyses", zap.Error(err))
}
if err := s.populateArtifactsInAnalyses(ctx, as...); err != nil {
return nil, err
}
artifacts := []*pacta.AnalysisArtifact{}
for _, a := range as {
artifacts = append(artifacts, a.Artifacts...)
}
if err := s.populateBlobsInAnalysisArtifacts(ctx, artifacts...); err != nil {
return nil, err
}
items, err := dereference(conv.AnalysesToOAPI(as))
if err != nil {
return nil, err
Expand Down
14 changes: 12 additions & 2 deletions cmd/server/pactasrv/conv/pacta_to_oapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,18 @@ func FileTypeToOAPI(ft pacta.FileType) (api.FileType, error) {
return api.FileTypeJSON, nil
case pacta.FileType_HTML:
return api.FileTypeHTML, nil
}
return "", fmt.Errorf("unknown file type: %q", ft)
case pacta.FileType_TEXT:
return api.FileTypeTEXT, nil
case pacta.FileType_CSS:
return api.FileTypeCSS, nil
case pacta.FileType_JS:
return api.FileTypeJS, nil
case pacta.FileType_TTF:
return api.FileTypeTTF, nil
case pacta.FileType_UNKNOWN:
return api.FileTypeUNKNOWN, nil
}
return api.FileTypeUNKNOWN, nil
}

func BlobToOAPI(b *pacta.Blob) (*api.Blob, error) {
Expand Down
3 changes: 3 additions & 0 deletions db/sqldb/blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func (d *DB) Blob(tx db.Tx, id pacta.BlobID) (*pacta.Blob, error) {
}

func (d *DB) Blobs(tx db.Tx, ids []pacta.BlobID) (map[pacta.BlobID]*pacta.Blob, error) {
if len(ids) == 0 {
return map[pacta.BlobID]*pacta.Blob{}, nil
}
ids = dedupeIDs(ids)
rows, err := d.query(tx, `
SELECT `+blobSelectColumns+`
Expand Down
44 changes: 44 additions & 0 deletions frontend/components/analysis/Editor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script setup lang="ts">
import {
type EditorAnalysisFields as EditorFields,
type EditorAnalysisValues as EditorValues,
} from '@/lib/editor'
interface Props {
editorValues: EditorValues
editorFields: EditorFields
}
interface Emits {
(e: 'update:editorValues', evs: EditorValues): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const efs = computed(() => props.editorFields)
const evs = computed({
get: () => props.editorValues,
set: (evs) => { emit('update:editorValues', evs) },
})
</script>

<template>
<div>
<FormEditorField
:editor-field="efs.name"
:editor-value="evs.name"
>
<PVInputText
v-model="evs.name.currentValue"
/>
</FormEditorField>
<FormEditorField
:editor-field="efs.description"
:editor-value="evs.description"
>
<PVTextarea
v-model="evs.description.currentValue"
auto-resize
/>
</FormEditorField>
</div>
</template>
194 changes: 194 additions & 0 deletions frontend/components/analysis/ListView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<script setup lang="ts">
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 pactaClient = usePACTA()
const { loading: { withLoading } } = useModal()
const i18n = useI18n()
const { t } = i18n
interface Props {
portfolios: Portfolio[]
portfolioGroups: PortfolioGroup[]
initiatives: Initiative[]
analyses: Analysis[]
selectedPortfolioIds: string[]
selectedPortfolioGroupIds: string[]
selectedAnalysisIds: string[]
}
const props = defineProps<Props>()
interface Emits {
(e: 'update:selectedPortfolioIds', value: string[]): void
(e: 'update:selectedPortfolioGroupIds', value: string[]): void
(e: 'update:selectedAnalysisIds', value: string[]): void
(e: 'refresh'): void
}
const emit = defineEmits<Emits>()
const refresh = () => { emit('refresh') }
const selectedAnalysisIDs = computed({
get: () => props.selectedAnalysisIds ?? [],
set: (value: string[]) => { emit('update:selectedAnalysisIds', value) },
})
interface EditorObject extends ReturnType<typeof analysisEditor> {
id: string
}
const prefix = 'components/analysis/ListView'
const tt = (s: string) => t(`${prefix}.${s}`)
const expandedRows = useState<EditorObject[]>(`${prefix}.expandedRows`, () => [])
const selectedRows = computed<EditorObject[]>({
get: () => {
const ids = selectedAnalysisIDs.value
return editorObjects.value.filter((editorObject) => ids.includes(editorObject.id))
},
set: (value: EditorObject[]) => {
const ids = value.map((row) => row.id)
ids.sort()
selectedAnalysisIDs.value = ids
},
})
const editorObjects = computed<EditorObject[]>(() => props.analyses.map((item) => ({ ...analysisEditor(item, i18n), id: item.id })))
const selectedAnalyses = computed<Analysis[]>(() => selectedRows.value.map((row) => row.currentValue.value))
const saveChanges = (id: string) => {
const index = editorObjects.value.findIndex((editorObject) => editorObject.id === id)
const eo = presentOrFileBug(editorObjects.value[index])
return withLoading(
() => pactaClient.updateAnalysis(id, eo.changes.value).then(refresh),
`${prefix}.saveChanges`,
)
}
const deleteAnalysis = (id: string) => withLoading(
() => pactaClient.deleteAnalysis(id),
`${prefix}.deleteAnalysis`,
)
const deleteSelected = () => Promise.all([selectedRows.value.map((row) => deleteAnalysis(row.id))]).then(refresh)
</script>

<template>
<div class="flex flex-column gap-3">
<div class="flex gap-2 flex-wrap">
<PVButton
icon="pi pi-refresh"
class="p-button-outlined p-button-secondary p-button-sm"
:label="tt('Refresh')"
@click="refresh"
/>
<PVButton
:disabled="!selectedRows || selectedRows.length === 0"
icon="pi pi-trash"
class="p-button-outlined p-button-danger p-button-sm"
:label="tt('Delete') + selectedCountSuffix(selectedRows)"
@click="deleteSelected"
/>
</div>
<PVDataTable
v-model:selection="selectedRows"
v-model:expanded-rows="expandedRows"
:value="editorObjects"
data-key="id"
size="small"
sort-field="editorValues.value.createdAt.originalValue"
:sort-order="-1"
>
<PVColumn selection-mode="multiple" />
<PVColumn
field="editorValues.value.createdAt.originalValue"
:header="tt('Created At')"
sortable
>
<template #body="slotProps">
{{ humanReadableTimeFromStandardString(slotProps.data.editorValues.value.createdAt.originalValue).value }}
</template>
</PVColumn>
<PVColumn
field="editorValues.value.name.originalValue"
sortable
:header="tt('Name')"
/>
<PVColumn
:header="tt('View')"
>
<template #body="slotProps">
<LinkButton
icon="pi pi-external-link"
class="p-button-outlined p-button-xs"
:label="tt('View')"
:to="`${apiServerURL}/report/${slotProps.data.id}`"
new-tab
/>
</template>
</PVColumn>
<PVColumn
expander
:header="tt('Details')"
/>
<template
#expansion="slotProps"
>
<div class="surface-100 p-3">
<h2 class="mt-0">
{{ tt('Metadata') }}
</h2>
<StandardDebug
always
:value="slotProps.data.currentValue.value"
label="Raw Data"
/>
<h2 class="mt-5">
{{ tt('Editable Properties') }}
</h2>
<AnalysisEditor
v-model:editor-values="slotProps.data.editorValues.value"
:editor-fields="slotProps.data.editorFields.value"
/>
<div class="flex gap-3 justify-content-between">
<PVButton
icon="pi pi-trash"
class="p-button-danger p-button-outlined"
:label="tt('Delete')"
@click="() => deleteAnalysis(slotProps.data.id)"
/>
<div v-tooltip.bottom="slotProps.data.saveTooltip">
<PVButton
:disabled="!slotProps.data.canSave.value"
:label="tt('Save Changes')"
icon="pi pi-save"
icon-pos="right"
@click="() => saveChanges(slotProps.data.id)"
/>
</div>
</div>
</div>
</template>
</PVDataTable>
<div class="flex flex-wrap gap-3 w-full justify-content-between">
<!-- TODO(grady) Hook this up to something. -->
<PVButton
class="p-button-outlined"
:label="tt('How To Run a Report')"
icon="pi pi-question-circle"
icon-pos="right"
/>
</div>
<StandardDebug
:value="selectedAnalyses"
label="Selected Analyses"
/>
<StandardDebug
:value="props.analyses"
label="All Analyses"
/>
</div>
</template>
38 changes: 27 additions & 11 deletions frontend/components/portfolio/ListView.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { portfolioEditor } from '@/lib/editor'
import { AnalysisType, type Portfolio, type PortfolioGroup, type Initiative } from '@/openapi/generated/pacta'
import { AnalysisType, type Portfolio, type PortfolioGroup, type Initiative, type Analysis } from '@/openapi/generated/pacta'
import { selectedCountSuffix } from '@/lib/selection'
const {
Expand All @@ -16,13 +16,16 @@ interface Props {
portfolios: Portfolio[]
portfolioGroups: PortfolioGroup[]
initiatives: Initiative[]
analyses: Analysis[]
selectedPortfolioIds: string[]
selectedPortfolioGroupIds: string[]
selectedAnalysisIds: string[]
}
const props = defineProps<Props>()
interface Emits {
(e: 'update:selectedPortfolioIds', value: string[]): void
(e: 'update:selectedPortfolioGroupIds', value: string[]): void
(e: 'update:selectedAnalysisIds', value: string[]): void
(e: 'refresh'): void
}
const emit = defineEmits<Emits>()
Expand Down Expand Up @@ -66,17 +69,20 @@ const saveChanges = (id: string) => {
)
}
const runAnalysis = (id: string) => {
const runAnalysis = (id: string, analysisType: AnalysisType) => {
const n = props.portfolios.find((p) => p.id === id)?.name ?? 'unknown'
return withLoading(
() => pactaClient.runAnalysis({
analysisType: AnalysisType.ANALYSIS_TYPE_REPORT,
name: 'Test Analysis!',
description: 'this is a test',
analysisType,
name: `${tt(analysisType)}: ${n}`,
description: `${tt(analysisType)} run at ${new Date().toLocaleString()} for portfolio ${n} (${id})`,
portfolioId: id,
}).then(() => { emit('refresh') }),
`${prefix}.saveChanges`,
}),
`${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),
Expand Down Expand Up @@ -211,6 +217,20 @@ const deleteSelected = () => Promise.all([selectedRows.value.map((row) => delete
@changed-memberships="refresh"
/>
</div>
<h2 class="mt-5">
{{ tt('Analyses') }}
</h2>
<PVMessage severity="warn">
TODO - filter analyses by those that match this portfolio
</PVMessage>
<PVButton
:label="tt('Run Audit')"
@click="() => runAudit(slotProps.data.id)"
/>
<PVButton
:label="tt('Run Report')"
@click="() => runReport(slotProps.data.id)"
/>
<h2 class="mt-5">
{{ tt('Editable Properties') }}
</h2>
Expand All @@ -234,10 +254,6 @@ const deleteSelected = () => Promise.all([selectedRows.value.map((row) => delete
@click="() => saveChanges(slotProps.data.id)"
/>
</div>
<PVButton
label="Test Run Analysis"
@click="() => runAnalysis(slotProps.data.id)"
/>
</div>
</div>
</template>
Expand Down
5 changes: 4 additions & 1 deletion frontend/components/portfolio/group/ListView.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { portfolioGroupEditor } from '@/lib/editor'
import { type Portfolio, type PortfolioGroup, type PortfolioGroupMembershipPortfolio } from '@/openapi/generated/pacta'
import { type Portfolio, type PortfolioGroup, type PortfolioGroupMembershipPortfolio, type Analysis } from '@/openapi/generated/pacta'
import { selectedCountSuffix } from '@/lib/selection'
const {
Expand All @@ -15,13 +15,16 @@ const { t } = i18n
interface Props {
portfolios: Portfolio[]
portfolioGroups: PortfolioGroup[]
analyses: Analysis[]
selectedPortfolioIds: string[]
selectedPortfolioGroupIds: string[]
selectedAnalysisIds: string[]
}
const props = defineProps<Props>()
interface Emits {
(e: 'update:selectedPortfolioIds', value: string[]): void
(e: 'update:selectedPortfolioGroupIds', value: string[]): void
(e: 'update:selectedAnalysisIds', value: string[]): void
(e: 'refresh'): void
}
const emit = defineEmits<Emits>()
Expand Down
Loading

0 comments on commit aa9899e

Please sign in to comment.