added websocket update repost generation complete

This commit is contained in:
Aurthur Musendame 2022-11-25 22:32:25 +02:00
parent d3c3abbfae
commit 2cd8ab17b3
18 changed files with 155 additions and 64 deletions

View file

@ -1,7 +1,10 @@
from typing import List, Optional
from datetime import datetime
import strawberry # noqa
from felicity.api.gql.analysis.types.analysis import AnalysisType
from felicity.api.gql.user.types import UserType
@strawberry.type
class Nothing:
@ -73,3 +76,23 @@ class LaggardData:
@strawberry.type
class LaggardStatistics:
data: List[LaggardData]
@strawberry.type
class ReportMetaType:
uid: int
period_start: datetime
period_end: datetime
date_column: str
location: Optional[str]
sample_states: Optional[str]
report_type: str
status: Optional[str]
temp: Optional[str]
analyses: Optional[List[AnalysisType]]
created_at: Optional[datetime]
created_by_uid: Optional[int]
created_by: Optional[UserType]
updated_at: Optional[datetime]
updated_by_uid: Optional[int]
updated_by: Optional[UserType]

View file

@ -4,11 +4,13 @@ from typing import List, Optional, Union
import strawberry # noqa
from felicity.api.gql.analysis.types.analysis import SampleType
from felicity.api.gql.analysis.types.results import AnalysisResultType
from felicity.api.gql.analytics.types import ReportMetaType
from felicity.api.gql.setup.types import DepartmentType
from felicity.api.gql.user.types import GroupType, UserType
from felicity.api.gql.worksheet.types import WorkSheetType
from felicity.apps.analysis.models.analysis import Sample
from felicity.apps.analysis.models.results import AnalysisResult
from felicity.apps.analytics.models import ReportMeta
from felicity.apps.worksheet.models import WorkSheet
@ -19,7 +21,7 @@ class UnknownObjectType:
actionObject = strawberry.union(
"actionObject",
[WorkSheetType, SampleType, AnalysisResultType],
[WorkSheetType, SampleType, AnalysisResultType, ReportMetaType],
description="Union of possible object types for streams",
)
@ -50,7 +52,7 @@ class ActivityStreamType:
@strawberry.field
async def action_object(
self, info
) -> Union[WorkSheetType, SampleType, AnalysisResultType, UnknownObjectType]:
) -> Union[WorkSheetType, SampleType, AnalysisResultType, ReportMetaType, UnknownObjectType]:
if self.action_object_type == "sample":
sample = await Sample.get(uid=self.action_object_uid)
return SampleType(
@ -70,6 +72,12 @@ class ActivityStreamType:
exclude=["right", "left", "tree_id", "level", "worksheet"]
), parent=None)
if self.action_object_type == "report":
report = await ReportMeta.get(uid=self.action_object_uid)
return ReportMetaType(**report.marshal_simple(
exclude=[]
))
return UnknownObjectType(
message=f"Please provide a resolver for object of type {self.action_object_type}"
)

View file

@ -6,12 +6,13 @@ from felicity.apps.analysis.models.analysis import Sample
from felicity.apps.analytics import SampleAnalyticsInit, conf, models
from felicity.apps.job import conf as job_conf
from felicity.apps.job import models as job_models
from felicity.apps.notification.utils import ReportNotifier
from felicity.apps.notification.utils import ReportNotifier, FelicityStreamer
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
report_notifier = ReportNotifier()
streamer = FelicityStreamer()
async def generate_report(job_uid: str):
@ -67,4 +68,5 @@ async def generate_report(job_uid: str):
f"Your {report.report_type} report was successfully generated",
report.created_by,
)
await streamer.stream(report, report.created_by, "generated", "report")
return True

View file

@ -37,8 +37,18 @@ const authFromStorage2 = (): {
}
const authLogout = () => {
localStorage.removeItem(STORAGE_AUTH_KEY);
}
export { authToStorage, authFromStorage,authFromStorage2, authLogout }
const getAuthData = () => {
let data: any = {};
if(localStorage.getItem(STORAGE_AUTH_KEY)){
const auth = JSON.parse(localStorage.getItem(STORAGE_AUTH_KEY)!)
data = { auth }
}
return data;
}
export { authToStorage, authFromStorage,authFromStorage2, authLogout, getAuthData }

View file

@ -1,26 +1,25 @@
import axios from 'axios';
import { useAuthStore } from "../stores"
import { getAuthData, authLogout } from "../auth"
import { REST_BASE_URL } from '../conf'
const getAuthHeaders = async () => {
const authStore = useAuthStore();
const authData = getAuthData();
if (authStore?.auth?.token) {
if (authData?.auth?.token) {
return {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Origin, Content-Type, X-Auth-Token',
...(authStore?.auth?.token && {
...(authData?.auth?.token && {
'x-felicity-user-id': "felicity-user",
'x-felicity-role': "felicity-administrator",
'Authorization': `Bearer ${authStore?.auth?.token}`
'Authorization': `Bearer ${authData?.auth?.token}`
}),
}
}
authStore.logout();
authLogout();
};
const axiosInstance = axios.create({

View file

@ -25,6 +25,5 @@
import { ref } from 'vue';
let show = ref(false);
</script>

View file

@ -0,0 +1,56 @@
import { toRefs, reactive } from 'vue'
import axios from "../axios/with-auth";
import useNotifyToast from "./alert_toast";
import { IReportListing } from '../models/reports';
const { toastSuccess, toastWarning } = useNotifyToast();
const state = reactive({
reports: [],
} as {
reports: IReportListing[];
});
export default function useAnalyticsComposable() {
const fetchReports = async () => {
await axios.get("reports").then((resp) => {
state.reports = resp.data;
})
}
const generateReport = async (payload) => {
await axios.post("reports", payload).then((resp) => {
state.reports.push(resp.data);
})
}
const deleteReport = async (report: IReportListing) => {
await axios.delete("reports/" + report.uid).then((resp) => {
const data = resp.data;
const index = state.reports.findIndex((x) => x.uid === data.uid);
if (index > -1) {
state.reports.splice(index, 1);
toastSuccess(data.message);
} else {
toastWarning("Failed to remove report: Please refresh your page");
}
})
}
const updateReport = (report: IReportListing) => {
const index = state.reports.findIndex((x) => x.uid === report.uid);
if (index > -1) {
state.reports[index] = {...state.reports[index], ...report }
}
}
return {
...toRefs(state),
fetchReports,
generateReport,
deleteReport,
updateReport
}
}

View file

@ -5,6 +5,7 @@ import userPreferenceComposable from "./preferences"
import useReportComposable from "./reports"
import useSampleComposable from "./samples"
import useWorkSheetComposable from "./worksheet"
// import useAnalyticsComposable from "./analytics"
export {
useNotifyToast,
@ -13,5 +14,6 @@ export {
userPreferenceComposable,
useReportComposable,
useSampleComposable,
useWorkSheetComposable
useWorkSheetComposable,
// useAnalyticsComposable
}

View file

@ -66,6 +66,11 @@ subscription getSystemActivity {
result
status
}
...on ReportMetaType {
uid
status
location
}
}
targetUid
verb

View file

@ -28,6 +28,7 @@ pinia.use(({ store }) => {
});
const app = createApp(App)
app.use(pinia)
app.component('font-awesome-icon', FontAwesomeIcon)
app.component('default-layout', LayoutDashboard)
app.component('empty-layout', LayoutEmpty)
@ -35,6 +36,5 @@ app.use(urql, urqlClient)
app.use(VueSweetalert2)
app.use(MotionPlugin)
app.use(router)
app.use(pinia)
app.mount('#app')

View file

@ -14,6 +14,9 @@ export interface IUser {
isSuperuser?: boolean;
authUid?: number;
auth?: IUserAuth;
// for API axios
first_name?: string;
last_name?: string;
}
export interface IUserAuth {

View file

@ -1,4 +1,4 @@
interface IPageInfo {
export interface IPageInfo {
endCursor?: string,
hasNextPage?: boolean,
hasPreviousPage?: boolean,

View file

@ -38,7 +38,6 @@ export const useAuthStore = defineStore('auth', () => {
const logout = () => {
toastInfo("Good bye " + auth.value.user?.firstName)
localStorage.removeItem(STORAGE_AUTH_KEY)
reset()
}

View file

@ -5,6 +5,7 @@ import { GET_ALL_CLIENTS,
GET_CLIENT_BY_UID } from '../graphql/clients.queries';
import { addListsUnique } from '../utils';
import { IClient, IClientContact } from '../models/client'
import { IPageInfo } from '../models/pagination'
import { useApiUtil } from '../composables'
@ -20,7 +21,7 @@ export const useClientStore = defineStore('client', {
clientContacts: [],
fetchingClientContacts: false,
clientCount: 0,
clientPageInfo: undefined,
clientPageInfo: {},
} as {
clients: IClient[];
fetchingClients: boolean;
@ -29,7 +30,7 @@ export const useClientStore = defineStore('client', {
clientContacts: IClientContact[];
fetchingClientContacts: boolean;
clientCount?: number;
clientPageInfo?: any;
clientPageInfo?: IPageInfo;
}
},
getters: {

View file

@ -6,6 +6,7 @@ import {
import { pipe, subscribe } from 'wonka';
import { useWorksheetStore } from './worksheet'
import { useSampleStore } from './sample'
import useAnalyticsComposable from '../composables/analytics';
export const useStreamStore = defineStore('stream', {
@ -19,11 +20,12 @@ export const useStreamStore = defineStore('stream', {
},
actions: {
addStream(payload){
const { updateReport } = useAnalyticsComposable()
const wsStore = useWorksheetStore()
const sampleStore = useSampleStore()
this.streams?.unshift(payload);
if(payload.actionObjectType === "sample"){
sampleStore.updateSampleStatus(payload.actionObject)
}
@ -32,6 +34,10 @@ export const useStreamStore = defineStore('stream', {
wsStore.updateWorksheetStatus(payload.actionObject)
}
if(payload.actionObjectType === "report"){
updateReport(payload.actionObject)
}
if(payload.actionObjectType === "result"){
sampleStore.updateAnalysesResultsStatus([payload.actionObject])
wsStore.updateAnalysesResults([payload.actionObject])

View file

@ -16,7 +16,7 @@ import { authExchange } from '@urql/exchange-auth';
import { SubscriptionClient } from 'subscriptions-transport-ws'
import { pipe, tap } from 'wonka'
import { useAuthStore } from "./stores"
import { getAuthData, authLogout } from "./auth"
import { GQL_BASE_URL, WS_BASE_URL } from './conf'
import { useNotifyToast } from './composables'
@ -27,13 +27,13 @@ const subscriptionClient = new SubscriptionClient( WS_BASE_URL, {
reconnect: true,
lazy: true,
connectionParams: () => {
const authStore = useAuthStore();
const authData = getAuthData();
return {
headers: {
...(authStore?.auth?.token && {
...(authData?.auth?.token && {
'x-felicity-user-id': "felicity-user-x",
'x-felicity-role': "felicity-role-x",
'Authorization': `Bearer ${authStore?.auth?.token}`
'Authorization': `Bearer ${authData?.auth?.token}`
})
},
}
@ -41,11 +41,11 @@ const subscriptionClient = new SubscriptionClient( WS_BASE_URL, {
});
const getAuth = async ({ authState }) => {
const authStore = useAuthStore();
const authData = getAuthData();
if (!authState) {
if (authStore?.auth?.token) {
return { token: authStore?.auth?.token };
if (authData?.auth?.token) {
return { token: authData?.auth?.token };
}
return null;
}
@ -57,7 +57,7 @@ const getAuth = async ({ authState }) => {
toastError("Faied to get Auth Data. Login");
authStore.logout();
authLogout();
return null;
};
@ -118,7 +118,7 @@ export const urqlClient = createClient({
}
if (isAuthError) {
toastError("Unknown Network Error Encountered")
useAuthStore().logout();
authLogout();
}
},
}),
@ -135,16 +135,16 @@ export const urqlClient = createClient({
}),
],
fetchOptions: () => {
const authStore = useAuthStore();
const authData = getAuthData();
return {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Origin, Content-Type, X-Auth-Token',
...(authStore?.auth?.token && {
...(authData?.auth?.token && {
'x-felicity-user-id': "felicity-user-x",
'x-felicity-role': "felicity-role-x",
'Authorization': `Bearer ${authStore?.auth?.token}`
'Authorization': `Bearer ${authData?.auth?.token}`
}),
},
};

View file

@ -82,7 +82,7 @@
function showMoreClients(): void {
clientParams.first = +clientBatch.value;
clientParams.after = clientPageInfo?.value?.endCursor;
clientParams.after = clientPageInfo?.value?.endCursor!;
clientParams.text = filterText.value;
clientParams.filterAction = false;
clientStore.fetchClients(clientParams);
@ -186,11 +186,11 @@
<div class="my-4 flex sm:flex-row flex-col">
<button @click.prevent="showMoreClients()"
class="px-2 py-1 mr-2 border-sky-800 border text-sky-800rounded-smtransition duration-300 hover:bg-sky-800 hover:text-white focus:outline-none"
:disabled="!pageInfo?.hasNextPage">Show More</button>
:disabled="!clientPageInfo?.hasNextPage">Show More</button>
<div class="flex flex-row mb-1 sm:mb-0">
<div class="relative">
<select class="appearance-none h-full rounded-l-sm border block w-full bg-white border-gray-400 text-gray-700 py-2 px-4 pr-8 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
v-model="clientBatch" :disabled="!pageInfo?.hasNextPage">
v-model="clientBatch" :disabled="!clientPageInfo?.hasNextPage">
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>

View file

@ -1,17 +1,15 @@
<script setup lang="ts">
import accordion from "../../components/Accordion.vue";
import { reactive, onMounted } from "vue";
import axios from "../../axios/with-auth";
import { REST_BASE_URL } from "../../conf";
import { useNotifyToast } from "../../composables";
import { useAnalysisStore } from "../../stores";
import { IReportListing } from "../../models/reports";
import useAnalyticsComposable from "../../composables/analytics"
const { toastSuccess, toastWarning } = useNotifyToast();
const analysisStore = useAnalysisStore();
const { reports, fetchReports, generateReport, deleteReport } = useAnalyticsComposable()
const state = reactive({
reports: [] as IReportListing[],
listingForm: {
report_type: "",
analyses_uids: [],
@ -29,17 +27,10 @@ onMounted(async () => {
text: "",
sortBy: ["name"],
});
await axios.get("reports").then((resp) => {
state.reports = resp.data;
});
await fetchReports();
});
const saveListingForm = () => {
const request = { ...state.listingForm };
axios.post("reports", request).then((resp) => {
state.reports.push(resp.data);
});
};
const saveListingForm = () => generateReport({ ...state.listingForm });
const downloadReport = (report: any) => {
const link = document.createElement("a");
@ -49,19 +40,6 @@ const downloadReport = (report: any) => {
link.click();
document.body.removeChild(link);
};
const deleteReport = (report: any) => {
axios.delete("reports/" + report.uid).then((resp) => {
const data = resp.data;
const index = state.reports.findIndex((x) => x.uid === data.uid);
if (index > -1) {
state!.reports.splice(index, 1);
toastSuccess(data.message);
} else {
toastWarning("Failed to remove report: Please refresh your page");
}
});
};
</script>
<template>
@ -70,7 +48,7 @@ const deleteReport = (report: any) => {
<template v-slot:body>
<div class="overflow-x-auto mt-4">
<div
v-if="state.reports?.length > 0"
v-if="reports?.length > 0"
class="align-middle inline-block min-w-full shadow overflow-hidden bg-white shadow-dashboard px-2 pt-1 rounded-bl-sm rounded-br-sm"
>
<table class="min-w-full">
@ -115,7 +93,7 @@ const deleteReport = (report: any) => {
</tr>
</thead>
<tbody class="bg-white">
<tr v-for="report in state.reports" :key="report.uid">
<tr v-for="report in reports" :key="report.uid">
<td>
<input type="checkbox" />
</td>