Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: introduce private and public preview feature flags #4473

Merged
merged 3 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 28 additions & 22 deletions apps/frontend-manage/src/components/common/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,28 +65,34 @@ function Header({ user }: HeaderProps): React.ReactElement {
active: router.pathname == '/courses',
data: { cy: 'courses' },
},
{
type: 'button',
key: 'resources-menubar-item',
label: t('manage.general.resources'),
icon: faBolt,
onClick: () => router.push('/resources'),
active: router.pathname == '/resources',
data: { cy: 'resources' },
className: { icon: 'text-orange-400' },
},
{
type: 'button',
key: 'catalog-menubar-item',
label: t('manage.general.catalog'),
icon: faBolt,
onClick: () => router.push('/catalog'),
active: router.pathname == '/catalog',
notification: pendingRequestData?.countCatalogSharingRequests !== 0,
data: { cy: 'catalog' },
className: { icon: 'text-orange-400' },
},
...(user?.featurePreview
...(user?.privatePreview
? ([
{
type: 'button',
key: 'resources-menubar-item',
label: t('manage.general.resources'),
icon: faBolt,
onClick: () => router.push('/resources'),
active: router.pathname == '/resources',
data: { cy: 'resources' },
className: { icon: 'text-orange-400' },
},
{
type: 'button',
key: 'catalog-menubar-item',
label: t('manage.general.catalog'),
icon: faBolt,
onClick: () => router.push('/catalog'),
active: router.pathname == '/catalog',
notification:
pendingRequestData &&
pendingRequestData.countCatalogSharingRequests !== 0,
data: { cy: 'catalog' },
className: { icon: 'text-orange-400' },
},
] as NavigationItemProps[])
: []),
...(user?.publicPreview
? [
{
type: 'dropdown',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function CourseOverviewHeader({
dataModal={{ cy: 'course-join-modal' }}
dataCloseButton={{ cy: 'course-join-modal-close' }}
/>
{user?.featurePreview ? (
{user?.publicPreview ? (
<Button
onClick={() => {
window.open(`/analytics/${course.id}/activity`, '_blank')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ function MicroLearningElement({
cy: `end-microlearning-${microLearning.name}`,
},
},
user?.featurePreview
user?.publicPreview
? {
label: (
<ActivityAnalyticsLink
Expand Down Expand Up @@ -444,7 +444,7 @@ function MicroLearningElement({
cy: `convert-microlearning-${microLearning.name}-to-practice-quiz`,
},
},
user?.featurePreview
user?.publicPreview
? {
label: (
<ActivityAnalyticsLink
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ function PracticeQuizElement({
cy: `duplicate-practice-quiz-${practiceQuiz.name}`,
},
}),
user?.featurePreview
user?.publicPreview
? {
label: (
<ActivityAnalyticsLink
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useQuery } from '@apollo/client'
import { faPieChart } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
CheckFeaturePreviewAvailableDocument,
CheckPublicPreviewAvailableDocument,
StackEvaluation,
} from '@klicker-uzh/graphql/dist/ops'
import { Button } from '@uzh-bf/design-system'
Expand Down Expand Up @@ -43,8 +43,7 @@ function EvaluationNavigation({
feedbacksAvailable,
}: EvaluationNavigationProps) {
const t = useTranslations()

const { data, loading } = useQuery(CheckFeaturePreviewAvailableDocument)
const { data, loading } = useQuery(CheckPublicPreviewAvailableDocument)

// automatically switch the active stack based on the active instance
useStackInstanceUpdates({
Expand Down Expand Up @@ -75,7 +74,7 @@ function EvaluationNavigation({
)}
<div className="flex flex-row items-center gap-4">
{!loading &&
data?.checkFeaturePreviewAvailable &&
data?.checkPublicPreviewAvailable &&
type === 'Asynchronous' ? (
<Button
className={{ root: 'flex h-8 flex-row gap-2' }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ function ElementFormErrors({
<div>{t('manage.formErrors.resolveErrors')}</div>
<ul className="ml-4 list-disc">
{errors.name && (
<li>{`${t('manage.questionForms.questionTitle')}: ${
errors.name
}`}</li>
<li>{`${t('manage.questionForms.elementTitle')}: ${errors.name}`}</li>
)}
{errors.tags && (
<li>{`${t('manage.questionPool.tags')}: ${errors.tags}`}</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function ElementInformationFields({
required={mode === ElementEditMode.CREATE}
contentPosition="popper"
disabled={mode === ElementEditMode.EDIT}
label={t('manage.questionForms.questionType')}
label={t('manage.questionForms.elementType')}
placeholder={t('manage.questionForms.selectQuestionType')}
items={questionTypeOptions}
data={{ cy: 'select-question-type' }}
Expand All @@ -59,7 +59,7 @@ function ElementInformationFields({
<FormikTextField
name="name"
required
label={t('manage.questionForms.questionTitle')}
label={t('manage.questionForms.elementTitle')}
tooltip={t('manage.questionForms.titleTooltip')}
className={{
root: 'w-full',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { ElementType } from '@klicker-uzh/graphql/dist/ops'
import { useQuery } from '@apollo/client'
import {
CheckPrivatePreviewAvailableDocument,
ElementType,
} from '@klicker-uzh/graphql/dist/ops'
import { useTranslations } from 'next-intl'

function useElementTypeOptions() {
const t = useTranslations()
const { data } = useQuery(CheckPrivatePreviewAvailableDocument, {
fetchPolicy: 'cache-first',
})

return [
const baseElements = [
{
value: ElementType.Sc,
label: t(`shared.${ElementType.Sc}.typeLabel`),
Expand Down Expand Up @@ -64,16 +71,21 @@ function useElementTypeOptions() {
)}`,
},
},
{
]

if (data?.checkPrivatePreviewAvailable) {
baseElements.push({
value: ElementType.Selection,
label: t(`shared.${ElementType.Selection}.typeLabel`),
data: {
cy: `select-question-type-${t(
`shared.${ElementType.Selection}.typeLabel`
)}`,
},
},
]
})
}

return baseElements
}

export default useElementTypeOptions
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ interface TagItemProps {
icon: IconDefinition[]
active: boolean
onClick: () => void
data?: { cy?: string; test?: string }
}

function TagItem({ text, icon, active, onClick }: TagItemProps) {
function TagItem({ text, icon, active, onClick, data }: TagItemProps) {
return (
<li
className={twMerge(
'hover:text-primary-100 px-2 py-0.5 hover:cursor-pointer',
active && 'text-primary-100'
)}
onClick={onClick}
data-cy={data?.cy}
data-test={data?.test}
>
<FontAwesomeIcon icon={active ? icon[1] : icon[0]} className="mr-2 w-4" />
{text}
Expand Down
72 changes: 44 additions & 28 deletions apps/frontend-manage/src/components/questions/tags/TagList.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useQuery } from '@apollo/client'
import {
faCheckCircle as faCheckCircleRegular,
faCircleXmark,
Expand All @@ -21,7 +22,11 @@ import {
faSquareCheck as faSquareCheckSolid,
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { ElementStatus, ElementType } from '@klicker-uzh/graphql/dist/ops'
import {
CheckPrivatePreviewAvailableDocument,
ElementStatus,
ElementType,
} from '@klicker-uzh/graphql/dist/ops'
import Loader from '@klicker-uzh/shared-components/src/Loader'
import { Button, Switch } from '@uzh-bf/design-system'
import { useTranslations } from 'next-intl'
Expand All @@ -30,17 +35,6 @@ import SuspendedTags from './SuspendedTags'
import TagHeader from './TagHeader'
import TagItem from './TagItem'

const elementTypeFilters: Record<ElementType, IconDefinition[]> = {
CONTENT: [faCommentRegular, faCommentSolid],
FLASHCARD: [faListRegular, faListSolid],
SC: [faQuestionRegular, faQuestionSolid],
MC: [faQuestionRegular, faQuestionSolid],
KPRIM: [faQuestionRegular, faQuestionSolid],
FREE_TEXT: [faQuestionRegular, faQuestionSolid],
NUMERICAL: [faQuestionRegular, faQuestionSolid],
SELECTION: [faSquareCheckRegular, faSquareCheckSolid],
}

const elementStatusFilters: Record<ElementStatus, IconDefinition[]> = {
DRAFT: [faPenRegular, faPenSolid],
REVIEW: [faEyeRegular, faEyeSolid],
Expand Down Expand Up @@ -90,6 +84,23 @@ function TagList({
}: Props): React.ReactElement {
const t = useTranslations()

const { data } = useQuery(CheckPrivatePreviewAvailableDocument, {
fetchPolicy: 'cache-first',
})
const elementTypeFilters: Record<ElementType, IconDefinition[] | undefined> =
{
CONTENT: [faCommentRegular, faCommentSolid],
FLASHCARD: [faListRegular, faListSolid],
SC: [faQuestionRegular, faQuestionSolid],
MC: [faQuestionRegular, faQuestionSolid],
KPRIM: [faQuestionRegular, faQuestionSolid],
FREE_TEXT: [faQuestionRegular, faQuestionSolid],
NUMERICAL: [faQuestionRegular, faQuestionSolid],
SELECTION: data?.checkPrivatePreviewAvailable
? [faSquareCheckRegular, faSquareCheckSolid]
: undefined,
}

const [questionStatusVisible, setQuestionStatusVisible] = useState(!compact)
const [questionTypesVisible, setQuestionTypesVisible] = useState(!compact)
const [userTagsVisible, setUserTagsVisible] = useState(!compact)
Expand Down Expand Up @@ -153,22 +164,27 @@ function TagList({

{questionTypesVisible && (
<ul className="list-none">
{Object.entries(elementTypeFilters).map(([type, icons]) => (
<TagItem
key={type}
text={t(`shared.${type as ElementType}.typeLabel`)}
icon={icons}
active={activeType === type}
onClick={(): void =>
handleTagClick({
tagName: type,
isTypeTag: true,
isStatusTag: false,
isUntagged: false,
})
}
/>
))}
{Object.entries(elementTypeFilters).map(([type, icons]) => {
if (!icons) return null

return (
<TagItem
key={type}
text={t(`shared.${type as ElementType}.typeLabel`)}
icon={icons}
active={activeType === type}
onClick={(): void =>
handleTagClick({
tagName: type,
isTypeTag: true,
isStatusTag: false,
isUntagged: false,
})
}
data={{ cy: `element-type-filter-${type}` }}
/>
)
})}
</ul>
)}

Expand Down
35 changes: 35 additions & 0 deletions cypress/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,41 @@ export default defineConfig({
await prisma.$disconnect()
}
},
async updateLecturerPermissions({
publicPreview,
privatePreview,
}: {
publicPreview: boolean
privatePreview: boolean
}) {
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL environment variable is not set')
}

const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
})

try {
const user = await prisma.user.update({
where: {
shortname: 'lecturer',
},
data: {
publicPreview,
privatePreview,
},
})

return !!user
} finally {
await prisma.$disconnect()
}
},
})
return config
},
Expand Down
36 changes: 0 additions & 36 deletions cypress/cypress/e2e/B-catalyst-workflow.cy.ts

This file was deleted.

Loading
Loading