Skip to content

Commit

Permalink
Merge pull request #27 from ucdavis/feature/iam
Browse files Browse the repository at this point in the history
Feature/iam
  • Loading branch information
bsedwards committed Aug 5, 2024
2 parents 1f1d7fc + d9fefbc commit fadcfd3
Show file tree
Hide file tree
Showing 32 changed files with 1,254 additions and 73 deletions.
18 changes: 18 additions & 0 deletions VueApp/src/Computing/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<main>
<component :is="$route.meta.layout || 'div'"
nav="viper-cats" navarea="false" highlightedTopNav="Computing">
</component>
</main>
<GenericError></GenericError>
</template>

<script>
import GenericError from '@/components/GenericError.vue'
export default {
name: 'Computing',
components: {
GenericError
}
}
</script>
27 changes: 27 additions & 0 deletions VueApp/src/Computing/computing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//load createApp function from vue
import { createApp } from 'vue'
//load pinia storage plugin
import { createPinia } from 'pinia';
//load the app router and application
import router from './router'
import App from './App.vue'
//import quasar, icon libraries, css, and our default quasar config
import { Quasar } from 'quasar'
import '@quasar/extras/material-icons/material-icons.css'
import 'quasar/dist/quasar.css'
import { useQuasarConfig } from '@/composables/QuasarConfig'
//import our css
import '@/assets/site.css'

const { quasarConfig } = useQuasarConfig()
const pinia = createPinia()
const app = createApp(App)

//make the API url available to any page or component in this app
app.provide("apiURL", import.meta.env.VITE_API_URL)

//load plugins
app.use(pinia)
app.use(router)
app.use(Quasar, quasarConfig)
app.mount('#myApp')
12 changes: 12 additions & 0 deletions VueApp/src/Computing/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VIPER - Computing</title>
</head>
<body>
<div id="myApp"></div>
<script type="module" src="./computing.ts"></script>
</body>
</html>
125 changes: 125 additions & 0 deletions VueApp/src/Computing/pages/BiorenderStudents.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<script setup lang="ts">
import { ref, inject } from 'vue'
import type { Ref } from 'vue'
import type { QTableProps } from 'quasar'
import { useFetch } from '@/composables/ViperFetch'
import { exportTable } from '@/composables/QuasarTableUtilities'
type StudentAssociation = {
collegeName: string | null,
majorName: string | null,
levelName: string | null,
className: string | null
}
type StudentInfo = {
email: string | null,
iamId: string | null,
firstName: string | null,
lastName: string | null,
studentType: string[] | null,
studentAssociations: StudentAssociation[] | null
}
type StudentInfoPerLine = {
email: string | null,
iamId: string | null,
firstName: string | null,
lastName: string | null,
studentType: string | null,
collegeSchool: string | null,
major: string | null,
level: string | null,
studentAssociation: StudentAssociation | null
}
const emails = ref("") as Ref<string>
const rows = ref([]) as Ref<StudentInfoPerLine[]>
const search = ref("")
const loading = ref(false)
const cols: QTableProps['columns'] = [
{ name: "email", label: "Email", field: "email", align: "left" },
{ name: "iamId", label: "Iam Id", field: "iamId", align: "left", sortable: true },
{ name: "firstName", label: "First Name", field: "firstName", align: "left", sortable: true },
{ name: "lastName", label: "Last Name", field: "lastName", align: "left", sortable: true },
{ name: "studentType", label: "Student Type", field: "studentType", align: "left", sortable: true },
{ name: "collegeSchool", label: "College / School", field: "collegeSchool", align: "left", sortable: true },
{ name: "major", label: "Major", field: "major", align: "left", sortable: true },
{ name: "level", label: "Level", field: "level", align: "left", sortable: true },
{ name: "studentAssociations", label: "Student Associations (College/Major/Level/Class)", field: "studentAssociation", align: "left", sortable: true },
]
const pagination = ref({ sortBy: "lastName", descending: false, rowsPerPage: 25 }) as Ref<any>
const stdUrl = inject('apiURL') + "people/biorenderStudents"
async function getData() {
rows.value = []
loading.value = true
const { post } = useFetch()
const url = new URL(stdUrl, document.baseURI)
var emailArray = emails.value.split(",")
const r = await post(url.toString(), emailArray)
let newValue = [] as StudentInfoPerLine[]
//create one line per student association
r.result.forEach((std: StudentInfo) => {
std.studentAssociations?.forEach((sa: StudentAssociation, i: number) => {
newValue.push({
iamId: std.iamId,
firstName: std.firstName,
lastName: std.lastName,
studentType: std.studentType == null || std.studentType[i] == null ? "" : std.studentType[i],
collegeSchool: sa.collegeName,
major: sa.majorName,
level: sa.levelName,
studentAssociation: sa,
email: std.email
})
})
})
rows.value = newValue
loading.value = false
}
function exportData() {
exportTable(cols, rows.value, ["studentAssociations"])
}
</script>
<template>
<h2>Biorender Student Lookup</h2>
<q-form class="q-mb-md">
<div class="row">
<q-input type="textarea" v-model="emails" dense outlined label="Emails (comma separated)" class="col col-12 col-md-6 col-lg-4"></q-input>
</div>
<q-btn dense no-caps color="primary" label="Get data" @click="getData()" class="q-px-md"></q-btn>
</q-form>

<q-table :rows="rows"
:columns="cols"
row-key="email"
dense
v-model:pagination="pagination"
:filter="search"
title="Student Info"
:loading="loading">
<template v-slot:top-right="props">
<q-btn dense no-caps label="Export" @click="exportData()" color="primary" class="q-px-md q-mr-md"></q-btn>
<q-input class="q-ml-xs q-mr-xs" v-model="search" dense outlined debounce="300" placeholder="Filter Results">
<template v-slot:append>
<q-icon name="filter_alt" />
</template>
</q-input>
</template>
<template v-slot:body-cell-studentAssociations="props">
<q-td :props="props">
{{props.row.studentAssociation.collegeName}}
/
{{props.row.studentAssociation.majorName}}
/
{{props.row.studentAssociation.levelName}}
/
{{props.row.studentAssociation.className}}
</q-td>
</template>
</q-table>
</template>
6 changes: 6 additions & 0 deletions VueApp/src/Computing/pages/Home.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script setup lang="ts">
import CmsContent from '@/components/CmsContent.vue'
</script>
<template>
<CmsContent contentName="computing-home"></CmsContent>
</template>
17 changes: 17 additions & 0 deletions VueApp/src/Computing/router/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createRouter, createWebHistory } from 'vue-router'
import routes from './routes'
import useRequireLogin from '@/composables/RequireLogin'

const baseUrl = import.meta.env.VITE_VIPER_HOME
const router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }),
history: createWebHistory(baseUrl),
routes,
})

router.beforeEach(async (to) => {
const { requireLogin } = useRequireLogin(to)
return requireLogin()
})

export default router
25 changes: 25 additions & 0 deletions VueApp/src/Computing/router/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import ViperLayout from '@/layouts/ViperLayout.vue'
//import ViperLayoutSimple from '@/layouts/ViperLayoutSimple.vue'

const routes = [
{
path: '/Computing/',
alias: '/Computing/Home',
meta: { layout: ViperLayout, allowUnAuth: false },
component: () => import('@/Computing/pages/Home.vue'),
name: "ExampleAppHome"
},
{
path: '/Computing/BiorenderStudents',
meta: { layout: ViperLayout, allowUnAuth: false },
component: () => import('@/Computing/pages/BiorenderStudents.vue'),
name: "AnotherPage"
},
{
path: '/:catchAll(.*)*',
meta: { layout: ViperLayout },
component: () => import('@/pages/Error404.vue')
}
]

export default routes
43 changes: 43 additions & 0 deletions VueApp/src/components/CmsContent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script setup lang="ts">
//Imports from vue
import type { Ref } from 'vue'
import { ref, inject, defineProps, watch } from 'vue'
//We'll use our fetch wrapper
import { useFetch } from '@/composables/ViperFetch'
//get the api root from our application settings
const baseUrl = inject("apiURL")
const props = defineProps({
contentName: {
type: String,
required: true
}
})
//creating a type is not necessary, but type checking can be helpful
type ContentBlock = {
content: string,
title: string
}
//create a reactive value for our content block
const cb = ref(null) as Ref<ContentBlock | null>
async function getContentBlock() {
const { get } = useFetch()
get(baseUrl + "cms/content/fn/" + props.contentName)
.then(r => cb.value = r.result)
}
watch(props, () => {
if (props.contentName.length) {
getContentBlock()
}
})
getContentBlock()
</script>
<template>
<div v-html="cb?.content"></div>
</template>
1 change: 0 additions & 1 deletion VueApp/src/components/GenericError.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
},
watch: {
showErrorMessage: function (v) {
console.log(this.showErrorMessage, this.status)
this.showError = this.showErrorMessage
this.showLogin = this.status !== undefined && this.status != null
}
Expand Down
62 changes: 62 additions & 0 deletions VueApp/src/composables/QuasarTableUtilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useQuasar, exportFile } from 'quasar'
import type { QTableProps } from 'quasar'


export function exportTable(columns: QTableProps['columns'], rows: any[], excludeFromExport: string[] | null = null) {
if (columns === undefined) {
return
}
// naive encoding to csv format
const columnsMinusExcludes = columns
.filter(c => excludeFromExport == null || excludeFromExport.findIndex(e => e == c.name) == -1)
const content = [columnsMinusExcludes.map(col => wrapCsvValue(col.label))]
.concat(
rows.map(row => columnsMinusExcludes
.map(col => wrapCsvValue(
typeof col.field === 'function'
? col.field(row)
: row[col.field === void 0 ? col.name : col.field],
col.format,
row))
.join(',')
))
.join('\r\n')

//const { useQuasar, exportFile } = Quasar
const $q = useQuasar();

const status = exportFile(
'table-export.csv',
content,
'text/csv'
)

if (status !== true) {
$q.notify({
message: 'Browser denied file download...',
color: 'negative',
icon: 'warning'
})
}
}

//Escape double quotes (" -> "") and new lines in the content
function wrapCsvValue(val: string, formatFn: any | null = null, row: any = null) {
let formatted = formatFn != null && formatFn !== void 0
? formatFn(val, row)
: val

formatted = formatted === void 0 || formatted === null
? ''
: String(formatted)

formatted = formatted.split('"').join('""')
/**
* Excel accepts \n and \r in strings, but some other CSV parsers do not
* Uncomment the next two lines to escape new lines
*/
.split('\n').join('\\n')
.split('\r').join('\\r')

return `"${formatted}"`
}
3 changes: 2 additions & 1 deletion VueApp/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ export default defineConfig(({ mode }) => ({
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
cts: resolve(__dirname, 'src/cts/index.html')
cts: resolve(__dirname, 'src/cts/index.html'),
computing: resolve(__dirname, 'src/computing/index.html'),
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions VueApp/vueapp.esproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<BuildOutputFolder>$(MSBuildProjectDirectory)\dist</BuildOutputFolder>
</PropertyGroup>
<ItemGroup>
<Folder Include="src\Computing\components\" />
<Folder Include="src\Computing\router\" />
<Folder Include="src\CTS\types\" />
<Folder Include="src\js\" />
<Folder Include="src\composables\" />
Expand Down
8 changes: 8 additions & 0 deletions test/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.

using System.Diagnostics.CodeAnalysis;

[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "<Pending>", Scope = "member", Target = "~F:Viper.test.RAPS.RolesTests.rapsContext")]
Loading

0 comments on commit fadcfd3

Please sign in to comment.