console: UI fixes for neon banner in connect db page

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6376
Co-authored-by: Rishichandra Wawhal <27274869+wawhal@users.noreply.github.com>
GitOrigin-RevId: 8f2ec961be370097b48b684211606d34dbaa002d
This commit is contained in:
Abhijeet Khangarot 2022-10-18 00:32:58 +05:30 committed by hasura-bot
parent aa70c62bdd
commit 49a2822dbe
8 changed files with 88 additions and 51 deletions

View File

@ -6,16 +6,16 @@ export function HerokuBanner() {
<div className="flex items-center"> <div className="flex items-center">
<GrHeroku size={15} className="mr-xs" color="#430098" /> <GrHeroku size={15} className="mr-xs" color="#430098" />
<div className="text-sm text-gray-700"> <div className="text-sm text-gray-700">
Heroku free database integration support has been deprecated. Heroku free database integration support has been deprecated.{' '}
</div>
<a <a
href="https://hasura.io/docs/latest/databases/connect-db/cloud-databases/heroku/" href="https://hasura.io/docs/latest/databases/connect-db/cloud-databases/heroku/"
className="ml-xs font-normal text-secondary italic text-sm" className="text-secondary italic"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
(Know More) (Know More)
</a> </a>
</div> </div>
</div>
); );
} }

View File

@ -40,9 +40,6 @@ export function NeonBanner(props: Props) {
<span className="ml-xs font-semibold flex items-center text-sm py-0.5 px-1.5 text-indigo-600 bg-indigo-100 rounded"> <span className="ml-xs font-semibold flex items-center text-sm py-0.5 px-1.5 text-indigo-600 bg-indigo-100 rounded">
Free Free
</span> </span>
<span className="ml-xs font-semibold flex items-center text-sm py-0.5 px-1.5 text-indigo-600 bg-indigo-100 rounded">
Preview
</span>
</div> </div>
<img <img
src="https://storage.googleapis.com/graphql-engine-cdn.hasura.io/cloud-console/assets/common/img/neon.jpg" src="https://storage.googleapis.com/graphql-engine-cdn.hasura.io/cloud-console/assets/common/img/neon.jpg"
@ -54,8 +51,9 @@ export function NeonBanner(props: Props) {
</div> </div>
<div className="flex justify-between items-center mb-sm"> <div className="flex justify-between items-center mb-sm">
<div className="w-[70%] text-md text-gray-700"> <div className="w-[70%] text-md text-gray-700">
Fully managed Postgres with separate storage and compute, that scales Modern, developer-friendly Postgres built for the cloud. Neon
to zero on inactivity and provides seamless scaling and branching. separates storage and compute to offer scale to zero and support
database branching.
</div> </div>
<div> <div>
<Button <Button

View File

@ -16,11 +16,11 @@ export function Neon(props: { allDatabases: string[]; dispatch: Dispatch }) {
// success callback // success callback
const pushToDatasource = (dataSourceName: string) => { const pushToDatasource = (dataSourceName: string) => {
// on success, refetch queries to show neon onboarding link in connect database page, // on success, refetch queries to show neon dashboard link in connect database page,
// overriding the stale time // overriding the stale time
reactQueryClient.refetchQueries(FETCH_NEON_PROJECTS_BY_PROJECTID_QUERYKEY); reactQueryClient.refetchQueries(FETCH_NEON_PROJECTS_BY_PROJECTID_QUERYKEY);
dispatch(_push(`/data/${dataSourceName}`)); dispatch(_push(`/data/${dataSourceName}/schema/public`));
}; };
const pushToConnectDBPage = () => { const pushToConnectDBPage = () => {
dispatch(_push(`/data/manage/connect`)); dispatch(_push(`/data/manage/connect`));

View File

@ -52,7 +52,7 @@ export function NeonOnboarding(props: {
const neonIntegrationStatus = useNeonIntegration( const neonIntegrationStatus = useNeonIntegration(
'default', 'default',
() => { () => {
// on success, refetch queries to show neon onboarding link in connect database page, // on success, refetch queries to show neon dashboard link in connect database page,
// overriding the stale time // overriding the stale time
reactQueryClient.refetchQueries( reactQueryClient.refetchQueries(
FETCH_NEON_PROJECTS_BY_PROJECTID_QUERYKEY FETCH_NEON_PROJECTS_BY_PROJECTID_QUERYKEY

View File

@ -30,8 +30,8 @@ export const NEON_ONBOARDING_QUERY_KEY = 'neonOnboarding';
export const experimentId = growthExperimentsIds.onboardingWizardV1; export const experimentId = growthExperimentsIds.onboardingWizardV1;
export const graphQlMutation = ` export const graphQlMutation = `
mutation trackExperimentsCohortActivity ($projectId: uuid!, $experimentId: String!, $kind: String!) { mutation trackExperimentsCohortActivity ($projectId: uuid!, $experimentId: String!, $kind: String! $error_code: String) {
trackExperimentsCohortActivity(experiment: $experimentId, payload: {kind: $kind, project_id: $projectId}) { trackExperimentsCohortActivity(experiment: $experimentId, payload: {kind: $kind, project_id: $projectId, error_code: $error_code}) {
status status
} }
} }

View File

@ -7,10 +7,10 @@ import {
type HasuraFamiliaritySurveyProps = { type HasuraFamiliaritySurveyProps = {
data: { question: string; options: IconCardGroupItem<string>[] }; data: { question: string; options: IconCardGroupItem<string>[] };
onOptionClick: (optionValue: string) => void; onOptionClick: (optionValue: string) => void;
onSkip: () => void; onSkip?: () => void;
}; };
export function HasuraFamiliaritySurvey(props: HasuraFamiliaritySurveyProps) { export function HasuraFamiliaritySurvey(props: HasuraFamiliaritySurveyProps) {
const { data, onOptionClick, onSkip } = props; const { data, onOptionClick } = props;
return ( return (
<> <>
@ -24,14 +24,15 @@ export function HasuraFamiliaritySurvey(props: HasuraFamiliaritySurveyProps) {
/> />
</div> </div>
</div> </div>
<div className="cursor-pointer text-secondary text-sm hover:text-secondary-dark"> {/* Remove skipping survey button, this change is experimental according to analytics data, so only commenting the code */}
{/* <div className="cursor-pointer text-secondary text-sm hover:text-secondary-dark">
<div <div
data-trackid="hasura-familiarity-survey-skip-button" data-trackid="hasura-familiarity-survey-skip-button"
onClick={onSkip} onClick={onSkip}
> >
Skip Skip
</div> </div>
</div> </div> */}
</> </>
); );
} }

View File

@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useMemo } from 'react';
import { useMutation, useQuery, useQueryClient } from 'react-query'; import { useMutation, useQuery, useQueryClient } from 'react-query';
import { APIError } from '@/hooks/error'; import { APIError } from '@/hooks/error';
import { IconCardGroupItem } from '@/new-components/IconCardGroup'; import { IconCardGroupItem } from '@/new-components/IconCardGroup';
@ -40,14 +40,19 @@ export function useFamiliaritySurveyData(): {
}, },
}); });
if (isLoading || isError) {
return emptySurveyResponseData;
}
const surveyQuestionData = data?.data?.survey?.find( const surveyQuestionData = data?.data?.survey?.find(
surveyInfo => surveyInfo.survey_name === surveyName surveyInfo => surveyInfo.survey_name === surveyName
)?.survey_questions?.[0]; )?.survey_questions?.[0];
const surveyOptions = useMemo(
() => mapOptionLabelToDetails(surveyQuestionData),
[surveyQuestionData]
);
if (isLoading || isError) {
return emptySurveyResponseData;
}
// skip showing the survey form if survey has no questions // skip showing the survey form if survey has no questions
if (!surveyQuestionData) { if (!surveyQuestionData) {
return emptySurveyResponseData; return emptySurveyResponseData;
@ -92,7 +97,7 @@ export function useFamiliaritySurveyData(): {
showFamiliaritySurvey, showFamiliaritySurvey,
data: { data: {
question: surveyQuestionData.question, question: surveyQuestionData.question,
options: mapOptionLabelToDetails(surveyQuestionData), options: surveyOptions,
}, },
onSkip, onSkip,
onOptionClick, onOptionClick,

View File

@ -6,14 +6,61 @@ import {
getFamiliaritySurveyOptionDetails, getFamiliaritySurveyOptionDetails,
} from './familiaritySurveyOptionDetails'; } from './familiaritySurveyOptionDetails';
export const mapOptionLabelToDetails = ( type UnroderedOptionArray = {
data: SurveysResponseData['data']['survey'][0]['survey_questions'][0]
): IconCardGroupItem<string>[] => {
const unroderedOptionArray: {
option: FamiliaritySurveyOptionCode; option: FamiliaritySurveyOptionCode;
id: string; id: string;
}[] = []; }[];
data.survey_question_options.forEach(optionData => {
/**
* Manually enforces the order of options as present in `familiaritySurveyOptionCode`
* array.
*/
// Not removing this function. Although it is not being used currenty due to dynamic product requirements,
// but can again be used in future.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const manuallyOrderOptions = (unroderedOptionArray: UnroderedOptionArray) => {
const surveyOptionDetails: IconCardGroupItem<string>[] = [];
familiaritySurveyOptionCode.forEach(optionCode => {
const optionData = unroderedOptionArray.find(
optionObj => optionObj.option === optionCode
);
if (optionData) {
const optionValues = getFamiliaritySurveyOptionDetails(
optionData.id,
optionCode
);
surveyOptionDetails.push(optionValues);
}
});
return surveyOptionDetails;
};
/**
* Randomly shuffles order of options of survey.
* The method comes from this algorithm: https://stackoverflow.com/a/46545530/7088648
*/
const randomlyOrderOptions = (unroderedOptionArray: UnroderedOptionArray) => {
const shuffledOptionArray = unroderedOptionArray
.map(value => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value);
const surveyOptionDetails: IconCardGroupItem<string>[] =
shuffledOptionArray.map(option =>
getFamiliaritySurveyOptionDetails(option.id, option.option)
);
return surveyOptionDetails;
};
export const mapOptionLabelToDetails = (
data?: SurveysResponseData['data']['survey'][0]['survey_questions'][0]
): IconCardGroupItem<string>[] => {
if (!data) return [];
// Array containing order of options as it comes from backend
const unroderedOptionArray: UnroderedOptionArray = [];
data?.survey_question_options?.forEach(optionData => {
const optionLabel = optionData.option.toLowerCase(); const optionLabel = optionData.option.toLowerCase();
if ( if (
@ -34,20 +81,6 @@ export const mapOptionLabelToDetails = (
}); });
}); });
// enforce order of options to show in the UI // return manuallyOrderOptions(unroderedOptionArray);
const surveyOptionDetails: IconCardGroupItem<string>[] = []; return randomlyOrderOptions(unroderedOptionArray);
familiaritySurveyOptionCode.forEach(optionCode => {
const optionData = unroderedOptionArray.find(
optionObj => optionObj.option === optionCode
);
if (optionData) {
const optionValues = getFamiliaritySurveyOptionDetails(
optionData.id,
optionCode
);
surveyOptionDetails.push(optionValues);
}
});
return surveyOptionDetails;
}; };