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

feat(ui-editor): enable component poc and feedback form #14210

Merged
merged 23 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"$schema": "news.schema.json",
"news": [
{
"date": "2024-11-29",
"title": "Prøv nytt design på Utforming!",
"content": "Vi tester en ny måte å legge til komponenter på Utforming-siden. Du kan velge det nye designet øverst til høyre på Utforming-siden og teste så mye du vil! Etterpå kan du gi tilbakemelding på hva du synes, ved å velge Gi tilbakemelding nederst til høyre."
},
{
"date": "2024-10-25",
"title": "Prosess har blitt voksen!",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
z-index: 99999999; /* Make sure this is on top of everything */
}

.disclaimer {
margin-top: var(--fds-spacing-2);
}

.submit {
margin-top: var(--fds-spacing-2);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const buttonTexts: ButtonTexts = {
};

const heading = 'Heading';
const description = 'Description';

describe('FeedbackForm', () => {
it('should render FeedbackForm', () => {
Expand Down Expand Up @@ -102,8 +103,10 @@ const renderFeedbackForm = ({
render(
<FeedbackFormContext.Provider value={{ answers: {}, setAnswers: setAnswers || jest.fn() }}>
<FeedbackForm
id='test'
buttonTexts={buttonTexts}
heading={heading}
description={description}
questions={questions}
position={position || 'inline'}
onSubmit={jest.fn()}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useRef } from 'react';
import { StudioButton, StudioModal } from '@studio/components';
import { StudioButton, StudioModal, StudioParagraph } from '@studio/components';
import type { ButtonTexts, QuestionConfig, QuestionsProps } from '../types/QuestionsProps';
import { YesNoQuestion } from './Question/YesNoQuestion';
import { useFeedbackFormContext } from '../contexts/FeedbackFormContext';
Expand All @@ -9,17 +9,23 @@ import { getDefaultAnswerValueForQuestion } from '../utils/questionUtils';
import type { AnswerType } from '../types/AnswerType';

type FeedbackFormProps = {
id: string;
buttonTexts: ButtonTexts;
heading: string;
description: string;
disclaimer?: string;
questions: QuestionConfig[];
position?: 'inline' | 'fixed';
onSubmit: (answers: Record<string, AnswerType>) => void;
};

export function FeedbackForm({
id,
questions,
buttonTexts,
heading,
description,
disclaimer,
position = 'inline',
onSubmit,
}: FeedbackFormProps): React.ReactElement {
Expand All @@ -42,7 +48,10 @@ export function FeedbackForm({
};

const handleSubmit = () => {
onSubmit(answers);
onSubmit({
...answers,
feedbackFormId: id,
});
handleCloseModal();
};

Expand Down Expand Up @@ -77,9 +86,17 @@ export function FeedbackForm({
closeButtonTitle={buttonTexts.close}
ref={modalRef}
>
wrt95 marked this conversation as resolved.
Show resolved Hide resolved
<StudioParagraph size='sm' spacing={true}>
{description}
</StudioParagraph>
{questions.map((question) => {
return renderQuestion(question);
})}
{disclaimer && (
<StudioParagraph size='xs' spacing={true} className={classes.disclaimer}>
{disclaimer}
</StudioParagraph>
)}
<StudioButton className={classes.submit} onClick={handleSubmit} color='success'>
{buttonTexts.submit}
</StudioButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import React, { type ChangeEvent } from 'react';
import type { QuestionsProps } from '../../types/QuestionsProps';
import { StudioTextfield } from '@studio/components';
import { StudioTextarea } from '@studio/components';
import { useDebounce } from '@studio/hooks';

export function TextQuestion({ id, label, value, onChange }: QuestionsProps): React.ReactElement {
const { debounce } = useDebounce({ debounceTimeInMs: 500 });
const debouncedOnChange = (newValue: string) => debounce(() => onChange(id, newValue));
return (
<StudioTextfield
<StudioTextarea
size='sm'
id={id}
label={label}
value={value}
onChange={(e: ChangeEvent<HTMLInputElement>) => debouncedOnChange(e.target.value)}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => debouncedOnChange(e.target.value)}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { StudioButton, StudioParagraph } from '@studio/components';
import { StudioButton, StudioLabelAsParagraph } from '@studio/components';
import { ThumbDownFillIcon, ThumbDownIcon, ThumbUpFillIcon, ThumbUpIcon } from '@studio/icons';
import type { QuestionsProps } from '../../types/QuestionsProps';
import classes from './YesNoQuestion.module.css';
Expand All @@ -26,7 +26,7 @@ export function YesNoQuestion({ id, label, value, buttonLabels, onChange }: YesN

return (
<div>
<StudioParagraph>{label}</StudioParagraph>
<StudioLabelAsParagraph size='sm'>{label}</StudioLabelAsParagraph>
<div className={classes.buttons}>
<StudioButton
variant='tertiary'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ describe('FeedbackFormImpl', () => {
it('should render FeedbackFormImpl', () => {
const feedbackForm = new FeedbackFormImpl({
onSubmit: jest.fn(),
id: 'test',
buttonTexts: {
submit: 'Submit',
trigger: 'Give feedback',
close: 'Close',
},
heading: 'Give feedback - heading',
description: 'Description',
questions: mockQuestions,
});

Expand All @@ -25,13 +27,15 @@ describe('FeedbackFormImpl', () => {
it('should open form modal when trigger button is clicked', async () => {
const user = userEvent.setup();
const feedbackForm = new FeedbackFormImpl({
id: 'test',
onSubmit: jest.fn(),
buttonTexts: {
submit: 'Submit',
trigger: 'Give feedback',
close: 'Close',
},
heading: 'Give feedback - heading',
description: 'Description',
questions: mockQuestions,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,45 @@ import { FeedbackForm } from '../FeedbackForm/FeedbackForm';
import type { AnswerType } from '../types/AnswerType';

export class FeedbackFormImpl {
private readonly id: string;
private readonly buttonTexts: ButtonTexts;
private readonly heading: string;
private readonly description: string;
private readonly disclaimer?: string;
private readonly questions: QuestionConfig[];
private readonly position: 'inline' | 'fixed' = 'inline';
private readonly onSubmit: (answers: Record<string, any>) => void;

constructor(config: {
id: string;
buttonTexts: ButtonTexts;
heading: string;
description: string;
disclaimer?: string;
questions: QuestionConfig[];
position?: 'inline' | 'fixed';
onSubmit: (answers: Record<string, AnswerType>) => void;
}) {
this.id = config.id;
this.buttonTexts = config.buttonTexts;
this.heading = config.heading;
this.description = config.description;
this.disclaimer = config.disclaimer;
this.questions = config.questions;
this.getFeedbackForm = this.getFeedbackForm.bind(this);
this.position = config.position || 'inline';
this.onSubmit = config.onSubmit;
}

public getFeedbackForm(): React.ReactNode {
public getFeedbackForm(): React.ReactElement {
return (
<FeedbackFormContextProvider>
<FeedbackForm
id={this.id}
buttonTexts={this.buttonTexts}
heading={this.heading}
description={this.description}
disclaimer={this.disclaimer}
questions={this.questions}
position={this.position}
onSubmit={this.onSubmit}
Expand Down
3 changes: 3 additions & 0 deletions frontend/packages/shared/src/api/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export const dataModelAddXsdFromRepoPath = (org, app, filePath) => `${basePath}/
// Deployment
// See frontend/app-development/utils/urlHelper.ts Deployments

// Feedback form
export const submitFeedbackPath = (org, app) => `${basePath}/${org}/${app}/feedbackform/submit`; // Post

// FormEditor
export const ruleHandlerPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/rule-handler?${s({ layoutSetName })}`; // Get, Post
export const widgetSettingsPath = (org, app) => `${basePath}/${org}/${app}/app-development/widget-settings`; // Get
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.componentButtonInline {
height: 80px;
width: 140px;
margin: 8px;
height: 60px;
width: 120px;
margin: 4px;
padding: 6px;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
.componentButton {
margin: 4px;
width: 140px;
height: 100px;
width: 130px;
height: 80px;
padding: 2px;
overflow: wrap;
}

.componentButtonInline {
height: 80px;
width: 140px;
margin: 8px;
height: 60px;
width: 120px;
margin: 4px;
padding: 2px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function ComponentButton({
<StudioButton
variant={selected ? 'primary' : 'secondary'}
onClick={onClick}
size='sm'
size='xs'
aria-label={tooltipContent}
className={inline ? classes.componentButtonInline : classes.componentButton}
title={tooltipContent}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FeedbackForm } from './FeedbackForm';
import axios from 'axios';

jest.mock('axios');
var mockedAxios = axios as jest.Mocked<typeof axios>;

describe('FeedbackForm', () => {
afterAll(() => {
jest.clearAllMocks();
});

it('should render feedback form', () => {
renderFeedbackForm();
expect(screen.getByRole('button', { name: 'Gi tilbakemelding' })).toBeInTheDocument();
});

it('should open the feedback form when clicking trigger', async () => {
const user = userEvent.setup();
renderFeedbackForm();
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
await user.click(screen.getByRole('button', { name: 'Gi tilbakemelding' }));
expect(await screen.findByRole('dialog')).toBeInTheDocument();
});

it('should close the feedback form when clicking send', async () => {
const user = userEvent.setup();
mockedAxios.post.mockResolvedValueOnce({});
renderFeedbackForm();
await user.click(screen.getByRole('button', { name: 'Gi tilbakemelding' }));
expect(await screen.findByRole('dialog')).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: 'Send' }));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
});

const renderFeedbackForm = () => {
return render(<FeedbackForm />);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { ReactElement } from 'react';
import { submitFeedbackPath } from 'app-shared/api/paths';
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import { post } from 'app-shared/utils/networking';
import { FeedbackFormImpl } from '@studio/feedback-form';
import { toast } from 'react-toastify';

/**
* This is a feedback form to gather feedback on the new design for adding components.
* It uses the FeedbackForm component from the @studio/feedback-form package.
* The form is temporary, and will be removed once the new design is fully tested, or we decide to go in a different direction.
* As such, all texts are hardcoded in Norwegian, to avoid adding unnecessary translations.
* @returns The FeedbackForm component.
*/
export function FeedbackForm(): ReactElement {
const { org, app } = useStudioEnvironmentParams();

const submitFeedback = async (answers: Record<string, string>) => {
wrt95 marked this conversation as resolved.
Show resolved Hide resolved
try {
// Using regular axios post rather than a mutation hook, since we are not storing
// the feedback in the cache, nor are we updating any state.
await post(submitFeedbackPath(org, app), { answers: { ...answers } });
toast.success('Takk for tilbakemeldingen!');
wrt95 marked this conversation as resolved.
Show resolved Hide resolved
nkylstad marked this conversation as resolved.
Show resolved Hide resolved
} catch (error) {
console.error('Failed to submit feedback', error);
toast.error('Noe gikk galt. Prøv igjen senere.');

Check warning on line 26 in frontend/packages/ux-editor/src/containers/DesignView/AddItem/FeedbackForm.tsx

View check run for this annotation

Codecov / codecov/patch

frontend/packages/ux-editor/src/containers/DesignView/AddItem/FeedbackForm.tsx#L25-L26

Added lines #L25 - L26 were not covered by tests
}
};

const feedbackForm = new FeedbackFormImpl({
id: 'add-component-poc-feedback',
onSubmit: submitFeedback,
buttonTexts: {
submit: 'Send',
trigger: 'Gi tilbakemelding',
close: 'Lukk',
},
heading: 'Gi tilbakemelding',
description:
'Hei! Så bra at du har testet det nye designet for å legge til komponenter! Vi vil gjerne høre hva du synes.',
disclaimer:
'Merk at KI-verktøy kan bli brukt til å analysere svarene. Påse at du ikke inkluderer personopplysninger i dine svar.',
position: 'fixed',
questions: [
{
id: 'bedreJaNei',
type: 'yesNo',
questionText: 'Likte du dette designet bedre?',
buttonLabels: {
yes: 'Ja',
no: 'Nei',
},
},
{
id: 'kommentar',
type: 'text',
questionText: 'Har du kommentarer eller forslag til forbedringer?',
},
],
});

return feedbackForm.getFeedbackForm();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.switchWrapper {
display: flex;
flex-direction: row;
gap: var(--fds-spacing-2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ToggleAddComponentPoc } from './ToggleAddComponentPoc';

describe('ToggleAddComponentPoc', () => {
it('should render the component', () => {
render(<ToggleAddComponentPoc />);
expect(screen.getByText('Prøv nytt design')).toBeInTheDocument();
});
});
Loading
Loading