Skip to content

Commit

Permalink
Standardizes + Improves Related-Analysis Display
Browse files Browse the repository at this point in the history
  • Loading branch information
gbdubs committed Jan 20, 2024
1 parent 5830d11 commit 2f77db0
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 73 deletions.
167 changes: 167 additions & 0 deletions frontend/components/analysis/ContextualListView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<script setup lang="ts">
import { AnalysisType, type Analysis } from '@/openapi/generated/pacta'
const { humanReadableTimeFromStandardString } = useTime()
const i18n = useI18n()
const localePath = useLocalePath()
const { t } = i18n
const prefix = 'components/analysis/ContextualListView'
const tt = (s: string) => t(`${prefix}.${s}`)
interface Props {
analyses: Analysis[]
name: string
portfolioId?: string | undefined
portfolioGroupId?: string | undefined
initiativeId?: string | undefined
}
const props = defineProps<Props>()
interface Emits {
(e: 'refresh'): void
}
const emit = defineEmits<Emits>()
const items = computed(() => props.analyses.map((a) => {
let status = tt('Running')
let statusBg = 'bg-yellow-200'
if (a.completedAt !== undefined) {
if (a.failureMessage !== undefined) {
status = tt('Failed')
statusBg = 'bg-red-400'
} else {
status = tt('Completed')
statusBg = 'bg-green-200'
}
} else if (new Date().getTime() - new Date(a.createdAt).getTime() > 1000 * 60 * 10) {
status = tt('PotentialTimeout')
statusBg = 'bg-red-200'
}
return {
...a,
status,
statusBg,
}
}))
const hasAnyAudits = computed(() => items.value.some((a) => a.analysisType === AnalysisType.ANALYSIS_TYPE_AUDIT))
const hasAnyReports = computed(() => items.value.some((a) => a.analysisType === AnalysisType.ANALYSIS_TYPE_REPORT))
const showPromptToRunAudit = computed(() => {
return !hasAnyAudits.value && !hasAnyReports.value
})
const showPromptToRunReport = computed(() => {
return hasAnyAudits.value && !hasAnyReports.value
})
const auditButtonClasses = computed(() => {
return !hasAnyAudits.value && !hasAnyReports.value ? '' : 'p-button-outlined'
})
const reportButtonClasses = computed(() => {
return !hasAnyReports.value && hasAnyAudits.value ? '' : 'p-button-outlined'
})
</script>

<template>
<div class="flex flex-column gap-2">
<PVDataTable
:value="items"
data-key="id"
size="medium"
sort-field="createdAt"
:sort-order="-1"
>
<template #empty>
<div class="p-3">
{{ tt('No Analyses Message') }}
</div>
</template>
<PVColumn
field="createdAt"
:header="tt('Ran At')"
>
<template #body="slotProps">
{{ humanReadableTimeFromStandardString(slotProps.data.createdAt).value }}
</template>
</PVColumn>
<PVColumn
field="status"
:header="tt('Status')"
>
<template #body="slotProps">
<AnalysisStatusChip
:analysis="slotProps.data"
/>
</template>
</PVColumn>
<PVColumn
field="analysisType"
:header="tt('Type')"
>
<template #body="slotProps">
{{ tt(slotProps.data.analysisType) }}
</template>
</PVColumn>
<PVColumn
field="name"
:header="tt('Name')"
/>
<PVColumn
field="Access"
:header="tt('Access')"
>
<template #body="slotProps">
<AnalysisAccessButtons
class="py-2"
:analysis="slotProps.data"
/>
</template>
</PVColumn>
<PVColumn :header="tt('Details')">
<template #body="slotProps">
<LinkButton
class="p-button-outlined p-button-xs p-button-secondary"
icon="pi pi-arrow-right"
:to="localePath(`/my-data?tab=a&analyses=${slotProps.data.id}`)"
/>
</template>
</PVColumn>
</PVDataTable>
<PVMessage
v-if="showPromptToRunAudit"
severity="info"
class="m-0"
:closable="false"
>
{{ tt('No Audits Message') }}
</PVMessage>
<PVMessage
v-if="showPromptToRunReport"
severity="info"
class="m-0"
:closable="false"
>
{{ tt('No Reports Message') }}
</PVMessage>
<div class="flex gap-2">
<AnalysisRunButton
:analysis-type="AnalysisType.ANALYSIS_TYPE_AUDIT"
:name="props.name"
:class="auditButtonClasses"
:portfolio-id="props.portfolioId"
:portfolio-group-id="props.portfolioGroupId"
:initiative-id="props.initiativeId"
class="p-button-sm"
@started="() => emit('refresh')"
/>
<AnalysisRunButton
:analysis-type="AnalysisType.ANALYSIS_TYPE_REPORT"
:name="props.name"
:class="reportButtonClasses"
:portfolio-id="props.portfolioId"
:portfolio-group-id="props.portfolioGroupId"
:initiative-id="props.initiativeId"
class="p-button-sm"
@started="() => emit('refresh')"
/>
</div>
</div>
</template>
13 changes: 12 additions & 1 deletion frontend/components/analysis/ListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ const deleteAnalysis = (id: string) => withLoading(
`${prefix}.deleteAnalysis`,
)
const deleteSelected = () => Promise.all([selectedRows.value.map((row) => deleteAnalysis(row.id))]).then(refresh)
const deleteSpecificAnalysis = async (id: string) => {
await deleteAnalysis(id)
refresh()
}
</script>

<template>
Expand Down Expand Up @@ -109,6 +113,13 @@ const deleteSelected = () => Promise.all([selectedRows.value.map((row) => delete
{{ humanReadableTimeFromStandardString(slotProps.data.currentValue.value.createdAt).value }}
</template>
</PVColumn>
<PVColumn :header="tt('Status')">
<template #body="slotProps">
<AnalysisStatusChip
:analysis="slotProps.data.currentValue.value"
/>
</template>
</PVColumn>
<PVColumn
field="currentValue.value.name"
sortable
Expand Down Expand Up @@ -151,7 +162,7 @@ const deleteSelected = () => Promise.all([selectedRows.value.map((row) => delete
icon="pi pi-trash"
class="p-button-danger p-button-outlined"
:label="tt('Delete')"
@click="() => deleteAnalysis(slotProps.data.id)"
@click="() => deleteSpecificAnalysis(slotProps.data.id)"
/>
<div v-tooltip.bottom="slotProps.data.saveTooltip">
<PVButton
Expand Down
51 changes: 51 additions & 0 deletions frontend/components/analysis/StatusChip.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script setup lang="ts">
import { type Analysis } from '@/openapi/generated/pacta'
const { t } = useI18n()
const prefix = 'components/analysis/StatusChip'
const tt = (s: string) => t(`${prefix}.${s}`)
interface Props {
analysis: Analysis
}
const props = defineProps<Props>()
const isStale = (a: Analysis): boolean => new Date().getTime() - new Date(a.createdAt).getTime() > 1000 * 60 * 10
const status = computed(() => {
if (props.analysis.completedAt !== undefined) {
if (props.analysis.failureMessage !== undefined) {
return tt('Failed')
} else {
return tt('Completed')
}
}
if (isStale(props.analysis)) {
return tt('PotentialTimeout')
}
return tt('Running')
})
const bg = computed(() => {
if (props.analysis.completedAt !== undefined) {
if (props.analysis.failureMessage !== undefined) {
return 'bg-red-400'
} else {
return 'bg-green-200'
}
}
if (isStale(props.analysis)) {
return 'bg-red-200'
}
return 'bg-yellow-200'
})
</script>

<template>
<div
:class="bg"
class="p-1 align-self-stretch flex align-items-center justify-content-center border-round-lg"
>
<span>
{{ status }}
</span>
</div>
</template>
73 changes: 7 additions & 66 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, type Analysis } from '@/openapi/generated/pacta'
import { type Portfolio, type PortfolioGroup, type Initiative, type Analysis } from '@/openapi/generated/pacta'
import { selectedCountSuffix } from '@/lib/selection'
const {
Expand Down Expand Up @@ -211,71 +211,12 @@ const deleteSelected = () => Promise.all([selectedRows.value.map((row) => delete
<h2 class="mt-5">
{{ tt('Analysis') }}
</h2>
<PVDataTable
:value="slotProps.data.analyses"
data-key="id"
size="medium"
sort-field="createdAt"
:sort-order="-1"
>
<template #empty>
<PVMessage severity="info">
{{ tt('No Analyses Message') }}
</PVMessage>
</template>
<PVColumn
field="createdAt"
:header="tt('Created At')"
>
<template #body="slotProps2">
{{ humanReadableTimeFromStandardString(slotProps2.data.createdAt).value }}
</template>
</PVColumn>
<PVColumn
field="analysisType"
:header="tt('Type')"
>
<template #body="slotProps2">
{{ tt(slotProps2.data.analysisType) }}
</template>
</PVColumn>
<PVColumn
field="name"
:header="tt('Name')"
/>
<PVColumn
field="Access"
:header="tt('Access')"
>
<template #body="slotProps2">
<AnalysisAccessButtons
class="py-2"
:analysis="slotProps2.data"
/>
</template>
</PVColumn>
<PVColumn :header="tt('Details')">
<template #body="slotProps2">
<LinkButton
class="p-button-outlined p-button-xs p-button-secondary"
icon="pi pi-arrow-right"
:to="localePath(`/my-data?tab=a&analyses=${slotProps2.data.id}`)"
/>
</template>
</PVColumn>
</PVDataTable>
<div class="flex gap-2 pt-2">
<AnalysisRunButton
:analysis-type="AnalysisType.ANALYSIS_TYPE_AUDIT"
:name="slotProps.data.currentValue.value.name"
:portfolio-id="slotProps.data.id"
/>
<AnalysisRunButton
:analysis-type="AnalysisType.ANALYSIS_TYPE_REPORT"
:name="slotProps.data.currentValue.value.name"
:portfolio-id="slotProps.data.id"
/>
</div>
<AnalysisContextualListView
:analyses="slotProps.data.analyses"
:name="slotProps.data.currentValue.value.name"
:portfolio-id="slotProps.data.id"
@refresh="refresh"
/>
<h2 class="mt-5">
{{ tt('Editable Properties') }}
</h2>
Expand Down
26 changes: 23 additions & 3 deletions frontend/components/portfolio/group/ListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,19 @@ const selectedPortfolioGroupIDs = computed({
interface EditorObject extends ReturnType<typeof portfolioGroupEditor> {
id: string
analyses: Analysis[]
}
const prefix = 'components/portfolio/group/ListView'
const tt = (s: string) => t(`${prefix}.${s}`)
const editorObjects = computed<EditorObject[]>(() => props.portfolioGroups.map((item) => ({ ...portfolioGroupEditor(item, i18n), id: item.id })))
const editorObjects = computed<EditorObject[]>(() => props.portfolioGroups.map(
(item) => ({
...portfolioGroupEditor(item, i18n),
id: item.id,
analyses: props.analyses.filter((a) => a.portfolioSnapshot.portfolioGroup?.id === item.id),
}),
))
const expandedRows = useState<EditorObject[]>(`${prefix}.expandedRows`, () => [])
const selectedRows = computed<EditorObject[]>({
Expand All @@ -68,7 +75,11 @@ const saveChanges = (id: string) => {
() => pactaClient.updatePortfolioGroup(id, eo.changes.value)
.then(() => pactaClient.findPortfolioGroupById(id))
.then((portfolio) => {
editorObjects.value[index] = { ...portfolioGroupEditor(portfolio, i18n), id }
editorObjects.value[index] = {
...portfolioGroupEditor(portfolio, i18n),
id,
analyses: props.analyses.filter((a) => a.portfolioSnapshot.portfolioGroup?.id === id),
}
}),
`${prefix}.saveChanges`,
)
Expand Down Expand Up @@ -147,13 +158,22 @@ const editorObjectToIds = (editorObject: EditorObject): string[] => {
>
<div class="surface-100 p-3">
<h2 class="mt-0">
Metadata
{{ tt('Metadata') }}
</h2>
<StandardDebug
always
:value="slotProps.data.currentValue.value"
label="Raw Data"
/>
<h2 class="mt-5">
{{ tt('Analysis') }}
</h2>
<AnalysisContextualListView
:analyses="slotProps.data.analyses"
:name="slotProps.data.currentValue.value.name"
:portfolio-group-id="slotProps.data.id"
@refresh="() => emit('refresh')"
/>
<h2 class="mt-5">
Editable Properties
</h2>
Expand Down
Loading

0 comments on commit 2f77db0

Please sign in to comment.