Skip to content

Commit

Permalink
feat(review): Satisfaction modal for advanced repository
Browse files Browse the repository at this point in the history
  • Loading branch information
xrutayisire committed Oct 12, 2023
1 parent 653b17a commit 99454d4
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 39 deletions.
1 change: 1 addition & 0 deletions packages/manager/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type {
PrismicRepository,
FrameworkWroomTelemetryID,
StarterId,
DocumentStatus,
} from "./managers/prismicRepository/types";

export type { SliceMachineManager } from "./managers/SliceMachineManager";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,25 @@ import {
TransactionalMergeReturnType,
FrameworkWroomTelemetryID,
StarterId,
DocumentStatus,
} from "./types";
import { assertPluginsInitialized } from "../../lib/assertPluginsInitialized";
import { UnauthenticatedError } from "../../errors";
import { UnauthenticatedError, UnexpectedDataError } from "../../errors";

const DEFAULT_REPOSITORY_SETTINGS = {
plan: "personal",
isAnnual: "false",
role: "developer",
};

const PrismicDocumentsCount = t.exact(
t.type({
count: t.number,
}),
);

type PrismicDocumentsCount = t.TypeOf<typeof PrismicDocumentsCount>;

type PrismicRepositoryManagerCheckExistsArgs = {
domain: string;
};
Expand All @@ -57,6 +66,14 @@ type PrismicRepositoryManagerPushDocumentsArgs = {
documents: Record<string, unknown>; // TODO: Type unknown if possible(?)
};

type PrismicRepositoryManagerGetDocumentsCountArgs = {
statuses: DocumentStatus[];
};

type PrismicRepositoryManagerGetDocumentsCountReturnType = {
count: number;
};

export class PrismicRepositoryManager extends BaseManager {
// TODO: Add methods for repository-specific actions. E.g. creating a
// new repository.
Expand Down Expand Up @@ -258,6 +275,54 @@ export class PrismicRepositoryManager extends BaseManager {
}
}

async getDocumentsCount(
args: PrismicRepositoryManagerGetDocumentsCountArgs,
): Promise<PrismicRepositoryManagerGetDocumentsCountReturnType> {
const { statuses } = args;
const statusParams = statuses.map((status) => `status=${status}`).join("&");
const url = new URL(
`./core/documents/count?${statusParams}`,
API_ENDPOINTS.PrismicWroom,
);

const repositoryName = await this.project.getRepositoryName();
// Update hostname to include repository domain
url.hostname = `${repositoryName}.${url.hostname}`;

const res = await this._fetch({
url,
method: "GET",
userAgent: PrismicRepositoryUserAgent.LegacyZero, // Custom User Agent is required,
});

if (res.ok) {
const json = await res.json();
const { value, error } = decode(PrismicDocumentsCount, json);

if (error || !value) {
throw new UnexpectedDataError(
`Received invalid data while getting documents count for repository "${repositoryName}."`,
);
}

return value;
}

let reason: string | null = null;
try {
reason = await res.text();
} catch {
// Noop
}

throw new Error(
`Failed to fetch documents count for repository "${repositoryName}.", ${res.status} ${res.statusText}`,
{
cause: reason,
},
);
}

async pushChanges(
args: TransactionalMergeArgs,
): Promise<TransactionalMergeReturnType> {
Expand Down
2 changes: 2 additions & 0 deletions packages/manager/src/managers/prismicRepository/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,5 @@ export type StarterId =
| "nuxt_multi_page"
| "nuxt_blog"
| "nuxt_multi_lang";

export type DocumentStatus = "published" | "draft";
6 changes: 5 additions & 1 deletion packages/manager/src/managers/telemetry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ type CommandInitEndSegmentEvent = SegmentEvent<

type ReviewSegmentEvent = SegmentEvent<
typeof SegmentEventType.review,
{ rating: number; comment: string }
{
rating: number;
comment: string;
type: "onboarding" | "advanced repository";
}
>;

type SliceSimulatorSetupSegmentEvent = SegmentEvent<
Expand Down
56 changes: 40 additions & 16 deletions packages/slice-machine/components/ReviewModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@ import { SliceMachineStoreType } from "@src/redux/type";
import { isModalOpen } from "@src/modules/modal";
import { isLoading } from "@src/modules/loading";
import { LoadingKeysEnum } from "@src/modules/loading/types";
import {
getLastSyncChange,
userHasSendAReview,
} from "@src/modules/userContext";
import { getLastSyncChange, getUserReview } from "@src/modules/userContext";
import useSliceMachineActions from "@src/modules/useSliceMachineActions";
import { ModalKeysEnum } from "@src/modules/modal/types";
import { telemetry } from "@src/apiClient";
import { selectAllCustomTypes } from "@src/modules/availableCustomTypes";
import { getLibraries } from "@src/modules/slices";
import { hasLocal } from "@lib/models/common/ModelData";
import { useDocumentsCount } from "@src/hooks/useDocumentsCount";
import { UserReview } from "@src/modules/userContext/types";

Modal.setAppElement("#__next");

Expand Down Expand Up @@ -63,18 +62,19 @@ const ReviewModal: React.FunctionComponent = () => {
const {
isReviewLoading,
isLoginModalOpen,
hasSendAReview,
userReview,
customTypes,
libraries,
lastSyncChange,
} = useSelector((store: SliceMachineStoreType) => ({
isReviewLoading: isLoading(store, LoadingKeysEnum.REVIEW),
isLoginModalOpen: isModalOpen(store, ModalKeysEnum.LOGIN),
hasSendAReview: userHasSendAReview(store),
userReview: getUserReview(store),
customTypes: selectAllCustomTypes(store),
libraries: getLibraries(store),
lastSyncChange: getLastSyncChange(store),
}));
const documentsCount = useDocumentsCount(["published"]);

const { skipReview, sendAReview, startLoadingReview, stopLoadingReview } =
useSliceMachineActions();
Expand Down Expand Up @@ -102,16 +102,38 @@ const ReviewModal: React.FunctionComponent = () => {
lastSyncChange && Date.now() - lastSyncChange >= 3600000
);

const userHasCreatedEnoughContent =
const isOnboardingDone =
sliceCount >= 1 &&
customTypes.length >= 1 &&
hasSliceWithinCustomType &&
hasPushedAnHourAgo;
const isAdvancedRepository =
documentsCount !== undefined && documentsCount >= 20;

const shouldDisplayOnboardingReview =
isOnboardingDone && !userReview.onboarding;

const shouldDisplayAdvancedRepositoryReview =
userReview.onboarding &&
isAdvancedRepository &&
!userReview.advancedRepository;

const reviewType: keyof UserReview = shouldDisplayAdvancedRepositoryReview
? "advancedRepository"
: "onboarding";

const onSendAReview = (rating: number, comment: string): void => {
startLoadingReview();
void telemetry.track({ event: "review", rating, comment });
sendAReview();
void telemetry.track({
event: "review",
rating,
comment,
type:
reviewType === "advancedRepository"
? "advanced repository"
: "onboarding",
});
sendAReview(reviewType);
stopLoadingReview();
};

Expand All @@ -123,9 +145,11 @@ const ReviewModal: React.FunctionComponent = () => {

return (
<SliceMachineModal
isOpen={userHasCreatedEnoughContent && !hasSendAReview}
isOpen={
shouldDisplayOnboardingReview || shouldDisplayAdvancedRepositoryReview
}
shouldCloseOnOverlayClick={false}
onRequestClose={() => skipReview()}
onRequestClose={() => skipReview(reviewType)}
closeTimeoutMS={500}
contentLabel={"Review Modal"}
portalClassName={"ReviewModal"}
Expand Down Expand Up @@ -178,9 +202,9 @@ const ReviewModal: React.FunctionComponent = () => {
}}
>
<Heading sx={{ fontSize: "20px", mr: 4 }}>
Share Feedback
Share feedback
</Heading>
<Close type="button" onClick={() => skipReview()} />
<Close type="button" onClick={() => skipReview(reviewType)} />
</Flex>
<Flex
sx={{
Expand All @@ -190,8 +214,8 @@ const ReviewModal: React.FunctionComponent = () => {
}}
>
<Text variant={"xs"} as={"p"} sx={{ maxWidth: 302, mb: 3 }}>
Overall, how satisfied are you with your Slice Machine
experience?
Overall, how satisfied or dissatisfied are you with your Slice
Machine experience so far?
</Text>
<Box
mb={2}
Expand All @@ -212,7 +236,7 @@ const ReviewModal: React.FunctionComponent = () => {
<Field
name={"comment"}
type="text"
placeholder="Share your thoughts. What can we improve?"
placeholder="Tell me more..."
as={Textarea}
autoComplete="off"
sx={{ height: 80, mb: 3 }}
Expand Down
22 changes: 22 additions & 0 deletions packages/slice-machine/src/hooks/useDocumentsCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useRequest } from "@prismicio/editor-support/Suspense";

import { managerClient } from "@src/managerClient";
import { DocumentStatus } from "@slicemachine/manager";

async function getDocumentsCount(...statuses: DocumentStatus[]) {
try {
const { count } = await managerClient.prismicRepository.getDocumentsCount({
statuses,
});
return count;
} catch (e) {
console.error("Error while trying to get documents count", e);
return undefined;
}
}

export function useDocumentsCount(
statuses: DocumentStatus[]
): number | undefined {
return useRequest(getDocumentsCount, statuses);
}
16 changes: 13 additions & 3 deletions packages/slice-machine/src/modules/useSliceMachineActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
renameAvailableCustomType,
} from "./availableCustomTypes";
import { createSlice, deleteSliceCreator, renameSliceCreator } from "./slices";
import { UserContextStoreType } from "./userContext/types";
import { UserContextStoreType, UserReview } from "./userContext/types";
import { GenericToastTypes, openToasterCreator } from "./toaster";
import {
initCustomTypeStoreCreator,
Expand Down Expand Up @@ -134,8 +134,18 @@ const useSliceMachineActions = () => {
dispatch(stopLoadingActionCreator({ loadingKey: LoadingKeysEnum.LOGIN }));

// UserContext module
const skipReview = () => dispatch(skipReviewCreator());
const sendAReview = () => dispatch(sendAReviewCreator());
const skipReview = (reviewType: keyof UserReview) =>
dispatch(
skipReviewCreator({
reviewType,
})
);
const sendAReview = (reviewType: keyof UserReview) =>
dispatch(
sendAReviewCreator({
reviewType,
})
);
const setUpdatesViewed = (versions: UserContextStoreType["updatesViewed"]) =>
dispatch(updatesViewedCreator(versions));
const setSeenSimulatorToolTip = () =>
Expand Down
26 changes: 20 additions & 6 deletions packages/slice-machine/src/modules/userContext/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ActionType, createAction, getType } from "typesafe-actions";
import {
AuthStatus,
UserContextStoreType,
UserReview,
} from "@src/modules/userContext/types";
import { refreshStateCreator } from "../environment";
import ErrorWithStatus from "@lib/models/common/ErrorWithStatus";
Expand All @@ -12,7 +13,10 @@ import { changesPushCreator } from "../pushChangesSaga";
// NOTE: Be careful every key written in this store is persisted in the localstorage

const initialState: UserContextStoreType = {
hasSendAReview: false,
userReview: {
onboarding: false,
advancedRepository: false,
},
updatesViewed: {
latest: null,
latestNonBreaking: null,
Expand All @@ -25,9 +29,13 @@ const initialState: UserContextStoreType = {
};

// Actions Creators
export const sendAReviewCreator = createAction("USER_CONTEXT/SEND_REVIEW")();
export const sendAReviewCreator = createAction("USER_CONTEXT/SEND_REVIEW")<{
reviewType: keyof UserReview;
}>();

export const skipReviewCreator = createAction("USER_CONTEXT/SKIP_REVIEW")();
export const skipReviewCreator = createAction("USER_CONTEXT/SKIP_REVIEW")<{
reviewType: keyof UserReview;
}>();

export const updatesViewedCreator = createAction("USER_CONTEXT/VIEWED_UPDATES")<
UserContextStoreType["updatesViewed"]
Expand Down Expand Up @@ -57,8 +65,11 @@ type userContextActions = ActionType<
>;

// Selectors
export const userHasSendAReview = (state: SliceMachineStoreType): boolean =>
state.userContext.hasSendAReview;
export const getUserReview = (state: SliceMachineStoreType): UserReview =>
state.userContext.userReview ?? {
onboarding: state.userContext.hasSendAReview ?? false,
advancedRepository: false,
};

export const getUpdatesViewed = (
state: SliceMachineStoreType
Expand Down Expand Up @@ -90,7 +101,10 @@ export const userContextReducer: Reducer<
case getType(skipReviewCreator):
return {
...state,
hasSendAReview: true,
userReview: {
...state.userReview,
[action.payload.reviewType]: true,
},
};
case getType(updatesViewedCreator): {
return {
Expand Down
15 changes: 13 additions & 2 deletions packages/slice-machine/src/modules/userContext/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,19 @@ export enum AuthStatus {
UNKNOWN = "unknown",
}

export type UserReview = {
onboarding: boolean;
advancedRepository: boolean;
};

// Allow to handle old property that users can have
// in their local storage
type LegacyUserContextStoreType = {
hasSendAReview?: boolean;
};

export type UserContextStoreType = {
hasSendAReview: boolean;
userReview: UserReview;
updatesViewed: {
latest: string | null;
latestNonBreaking: string | null;
Expand All @@ -16,4 +27,4 @@ export type UserContextStoreType = {
hasSeenChangesToolTip: boolean;
authStatus: AuthStatus;
lastSyncChange: number | null;
};
} & LegacyUserContextStoreType;
Loading

0 comments on commit 99454d4

Please sign in to comment.