fix: filter search records based on roles
This commit is contained in:
2
frontend/components.d.ts
vendored
2
frontend/components.d.ts
vendored
@@ -90,6 +90,8 @@ declare module 'vue' {
|
|||||||
LucideArrowDown: typeof import('~icons/lucide/arrow-down')['default']
|
LucideArrowDown: typeof import('~icons/lucide/arrow-down')['default']
|
||||||
LucideArrowUp: typeof import('~icons/lucide/arrow-up')['default']
|
LucideArrowUp: typeof import('~icons/lucide/arrow-up')['default']
|
||||||
LucideCornerDownLeft: typeof import('~icons/lucide/corner-down-left')['default']
|
LucideCornerDownLeft: typeof import('~icons/lucide/corner-down-left')['default']
|
||||||
|
LucideSearch: typeof import('~icons/lucide/search')['default']
|
||||||
|
LucideX: typeof import('~icons/lucide/x')['default']
|
||||||
Members: typeof import('./src/components/Settings/Members.vue')['default']
|
Members: typeof import('./src/components/Settings/Members.vue')['default']
|
||||||
MobileLayout: typeof import('./src/components/MobileLayout.vue')['default']
|
MobileLayout: typeof import('./src/components/MobileLayout.vue')['default']
|
||||||
MultiSelect: typeof import('./src/components/Controls/MultiSelect.vue')['default']
|
MultiSelect: typeof import('./src/components/Controls/MultiSelect.vue')['default']
|
||||||
|
|||||||
@@ -18,11 +18,17 @@
|
|||||||
|
|
||||||
<div class="max-h-96 overflow-auto mb-2">
|
<div class="max-h-96 overflow-auto mb-2">
|
||||||
<div v-if="query.length" class="mt-5 space-y-5">
|
<div v-if="query.length" class="mt-5 space-y-5">
|
||||||
<CommandPaletteGroup :list="searchResults" />
|
<CommandPaletteGroup
|
||||||
|
:list="searchResults"
|
||||||
|
@navigateTo="navigateTo"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="mt-5 space-y-5">
|
<div v-else class="mt-5 space-y-5">
|
||||||
<CommandPaletteGroup :list="jumpToOptions" />
|
<CommandPaletteGroup
|
||||||
|
:list="jumpToOptions"
|
||||||
|
@navigateTo="navigateTo"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -61,7 +67,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { createResource, debounce, Dialog } from 'frappe-ui'
|
import { createResource, debounce, Dialog } from 'frappe-ui'
|
||||||
import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
|
import { nextTick, onMounted, ref, watch } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
BookOpen,
|
BookOpen,
|
||||||
Briefcase,
|
Briefcase,
|
||||||
@@ -72,7 +79,6 @@ import {
|
|||||||
Search,
|
Search,
|
||||||
Users,
|
Users,
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import CommandPaletteGroup from './CommandPaletteGroup.vue'
|
import CommandPaletteGroup from './CommandPaletteGroup.vue'
|
||||||
|
|
||||||
const show = defineModel<boolean>({ required: true, default: false })
|
const show = defineModel<boolean>({ required: true, default: false })
|
||||||
@@ -146,6 +152,13 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(show, () => {
|
||||||
|
if (!show.value) {
|
||||||
|
query.value = ''
|
||||||
|
searchResults.value = []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
addKeyboardShortcuts()
|
addKeyboardShortcuts()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
v-for="item in result.items"
|
v-for="item in result.items"
|
||||||
class="flex items-center justify-between p-2 rounded hover:bg-surface-gray-2 cursor-pointer"
|
class="flex items-center justify-between p-2 rounded hover:bg-surface-gray-2 cursor-pointer"
|
||||||
:class="{ 'bg-surface-gray-2': item.isActive }"
|
:class="{ 'bg-surface-gray-2': item.isActive }"
|
||||||
|
@click="emit('navigateTo', item.route)"
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<component
|
<component
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
<div v-html="item.title"></div>
|
<div v-html="item.title"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.modified" class="text-ink-gray-5">
|
<div v-if="item.modified" class="text-ink-gray-5">
|
||||||
{{ item.modified }} {{ dayjs.unix(item.modified).fromNow(true) }}
|
{{ dayjs.unix(item.modified).fromNow(true) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,9 +27,9 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject } from 'vue'
|
import { inject } from 'vue'
|
||||||
import { dayjsLocal } from 'frappe-ui'
|
|
||||||
|
|
||||||
const dayjs = inject<any>('$dayjs')
|
const dayjs = inject<any>('$dayjs')
|
||||||
|
const emit = defineEmits(['navigateTo'])
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
list: Array<{
|
list: Array<{
|
||||||
|
|||||||
@@ -192,7 +192,6 @@ import PageModal from '@/components/Modals/PageModal.vue'
|
|||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import InviteIcon from '@/components/Icons/InviteIcon.vue'
|
|
||||||
import {
|
import {
|
||||||
ref,
|
ref,
|
||||||
onMounted,
|
onMounted,
|
||||||
@@ -225,10 +224,10 @@ import {
|
|||||||
minimize,
|
minimize,
|
||||||
IntermediateStepModal,
|
IntermediateStepModal,
|
||||||
} from 'frappe-ui/frappe'
|
} from 'frappe-ui/frappe'
|
||||||
import InviteIcon from './Icons/InviteIcon.vue'
|
import InviteIcon from '@/components/Icons/InviteIcon.vue'
|
||||||
import UserDropdown from '@/components/UserDropdown.vue'
|
import UserDropdown from '@/components/Sidebar/UserDropdown.vue'
|
||||||
import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue'
|
import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue'
|
||||||
import SidebarLink from '@/components/SidebarLink.vue'
|
import SidebarLink from '@/components/Sidebar/SidebarLink.vue'
|
||||||
import CommandPalette from '@/components/CommandPalette/CommandPalette.vue'
|
import CommandPalette from '@/components/CommandPalette/CommandPalette.vue'
|
||||||
|
|
||||||
const { user } = sessionStore()
|
const { user } = sessionStore()
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const apps = createResource({
|
|||||||
name: 'frappe',
|
name: 'frappe',
|
||||||
logo: '/assets/lms/images/desk.png',
|
logo: '/assets/lms/images/desk.png',
|
||||||
title: __('Desk'),
|
title: __('Desk'),
|
||||||
route: '/app',
|
route: '/desk/lms',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
data.map((app) => {
|
data.map((app) => {
|
||||||
|
|||||||
@@ -65,6 +65,6 @@ export default defineConfig(({ mode }) => ({
|
|||||||
'highlight.js',
|
'highlight.js',
|
||||||
'plyr',
|
'plyr',
|
||||||
],
|
],
|
||||||
exclude: mode === 'production' ? [] : ['frappe-ui'],
|
//exclude: mode === 'production' ? [] : ['frappe-ui'],
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import frappe
|
import frappe
|
||||||
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -12,14 +13,20 @@ def search_sqlite(query: str):
|
|||||||
except LearningSearchIndexMissingError:
|
except LearningSearchIndexMissingError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
return prepare_search_results(result)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_search_results(result):
|
||||||
|
roles = frappe.get_roles()
|
||||||
groups = {}
|
groups = {}
|
||||||
print(result)
|
|
||||||
for r in result["results"]:
|
for r in result["results"]:
|
||||||
doctype = r["doctype"]
|
doctype = r["doctype"]
|
||||||
|
if doctype == "LMS Course" and can_access_course(r, roles):
|
||||||
if doctype == "LMS Course":
|
r["author_info"] = get_author_info(r.get("author"))
|
||||||
groups.setdefault("Courses", []).append(r)
|
groups.setdefault("Courses", []).append(r)
|
||||||
elif doctype == "LMS Batch":
|
elif doctype == "LMS Batch" and can_access_batch(r, roles):
|
||||||
|
r["author_info"] = get_author_info(r.get("author"))
|
||||||
groups.setdefault("Batches", []).append(r)
|
groups.setdefault("Batches", []).append(r)
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
@@ -27,3 +34,31 @@ def search_sqlite(query: str):
|
|||||||
out.append({"title": key, "items": groups[key]})
|
out.append({"title": key, "items": groups[key]})
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def can_access_course(course, roles):
|
||||||
|
if can_create_course(roles):
|
||||||
|
return True
|
||||||
|
elif course.get("published"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def can_access_batch(batch, roles):
|
||||||
|
if can_create_batch(roles):
|
||||||
|
return True
|
||||||
|
elif batch.get("published") and batch.get("start_date") >= nowdate():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def can_create_course(roles):
|
||||||
|
return "Course Creator" in roles or "Moderator" in roles
|
||||||
|
|
||||||
|
|
||||||
|
def can_create_batch(roles):
|
||||||
|
return "Batch Evaluator" in roles or "Moderator" in roles
|
||||||
|
|
||||||
|
|
||||||
|
def get_author_info(owner):
|
||||||
|
return frappe.db.get_value("User", owner, ["full_name", "user_image", "username", "email"], as_dict=True)
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ from contextlib import suppress
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.search.sqlite_search import SQLiteSearch, SQLiteSearchIndexMissingError
|
from frappe.search.sqlite_search import SQLiteSearch, SQLiteSearchIndexMissingError
|
||||||
from frappe.utils import update_progress_bar
|
from frappe.utils import nowdate
|
||||||
from redis.exceptions import ResponseError
|
|
||||||
|
|
||||||
|
|
||||||
class LearningSearch(SQLiteSearch):
|
class LearningSearch(SQLiteSearch):
|
||||||
INDEX_NAME = "learning.db"
|
INDEX_NAME = "learning.db"
|
||||||
|
|
||||||
INDEX_SCHEMA = {
|
INDEX_SCHEMA = {
|
||||||
"metadata_fields": ["category", "owner", "published"],
|
"metadata_fields": ["category", "owner", "published", "published_on", "start_date"],
|
||||||
"tokenizer": "unicode61 remove_diacritics 2 tokenchars '-_'",
|
"tokenizer": "unicode61 remove_diacritics 2 tokenchars '-_'",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,31 +63,6 @@ class LearningSearch(SQLiteSearch):
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
def can_create_course(self, roles):
|
|
||||||
return "Course Creator" in roles or "Moderator" in roles
|
|
||||||
|
|
||||||
def can_create_batch(self, roles):
|
|
||||||
return "Batch Evaluator" in roles or "Moderator" in roles
|
|
||||||
|
|
||||||
def get_records(self, doctype):
|
|
||||||
records = []
|
|
||||||
roles = frappe.get_roles()
|
|
||||||
filters = {}
|
|
||||||
|
|
||||||
if doctype == "LMS Course":
|
|
||||||
if not self.can_create_course(roles):
|
|
||||||
filters = {"published": 1}
|
|
||||||
|
|
||||||
if doctype == "LMS Batch":
|
|
||||||
if not self.can_create_batch(roles):
|
|
||||||
filters = {"published": 1}
|
|
||||||
|
|
||||||
records = frappe.db.get_all(doctype, filters=filters, fields=self.DOCTYPE_FIELDS[doctype])
|
|
||||||
for record in records:
|
|
||||||
record["doctype"] = doctype
|
|
||||||
|
|
||||||
return records
|
|
||||||
|
|
||||||
def build_index(self):
|
def build_index(self):
|
||||||
try:
|
try:
|
||||||
super().build_index()
|
super().build_index()
|
||||||
@@ -96,11 +70,21 @@ class LearningSearch(SQLiteSearch):
|
|||||||
frappe.throw(e)
|
frappe.throw(e)
|
||||||
|
|
||||||
def get_search_filters(self):
|
def get_search_filters(self):
|
||||||
roles = frappe.get_roles()
|
|
||||||
if not (self.can_create_course(roles) and self.can_create_batch(roles)):
|
|
||||||
return {"published": 1}
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@SQLiteSearch.scoring_function
|
||||||
|
def get_doctype_boost(self, row, query, query_words):
|
||||||
|
doctype = row["doctype"]
|
||||||
|
if doctype == "LMS Course":
|
||||||
|
if row["published"]:
|
||||||
|
return 1.3
|
||||||
|
elif doctype == "LMS Batch":
|
||||||
|
if row["published"] and row["start_date"] >= nowdate():
|
||||||
|
return 1.3
|
||||||
|
elif row["published"]:
|
||||||
|
return 1.2
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
|
||||||
class LearningSearchIndexMissingError(SQLiteSearchIndexMissingError):
|
class LearningSearchIndexMissingError(SQLiteSearchIndexMissingError):
|
||||||
pass
|
pass
|
||||||
|
|||||||
Reference in New Issue
Block a user