Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/tough-jokes-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@neo4j/graphql-toolbox": minor
---

feat: GraphQL Toolbox product usage tracking - UX parts
3 changes: 3 additions & 0 deletions .github/workflows/toolbox-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ jobs:
- name: Build graphql-toolbox
run: yarn build
working-directory: packages/graphql-toolbox
env:
SEGMENT_GRAPHQL_TOOLBOX_DEV_SOURCE: ${{ secrets.SEGMENT_GRAPHQL_TOOLBOX_DEV_SOURCE }}
SEGMENT_GRAPHQL_TOOLBOX_PROD_SOURCE: ${{ secrets.SEGMENT_GRAPHQL_TOOLBOX_PROD_SOURCE }}
- uses: jakejarvis/s3-sync-action@be0c4ab89158cac4278689ebedd8407dd5f35a83 # renovate: tag=v0.5.1
with:
args: --acl public-read --follow-symlinks
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ui-stage-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:
- name: UI build
env:
PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
SEGMENT_GRAPHQL_TOOLBOX_DEV_SOURCE: ${{ secrets.SEGMENT_GRAPHQL_TOOLBOX_DEV_SOURCE }}
SEGMENT_GRAPHQL_TOOLBOX_PROD_SOURCE: ${{ secrets.SEGMENT_GRAPHQL_TOOLBOX_PROD_SOURCE }}
run: |
yarn
yarn build
Expand Down
2 changes: 2 additions & 0 deletions packages/graphql-toolbox/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SEGMENT_GRAPHQL_TOOLBOX_DEV_SOURCE=thesecretkey
SEGMENT_GRAPHQL_TOOLBOX_PROD_SOURCE=thesecretkey
19 changes: 10 additions & 9 deletions packages/graphql-toolbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,21 @@
"@neo4j-ndl/base": "0.8.5",
"@neo4j-ndl/react": "0.9.0",
"@neo4j/graphql": "3.10.1",
"@neo4j/introspector": "^1.0.2",
"codemirror": "^5.65.8",
"codemirror-graphql": "^2.0.0",
"graphiql-explorer": "^0.9.0",
"@neo4j/introspector": "1.0.2",
"codemirror": "5.65.8",
"codemirror-graphql": "2.0.0",
"dotenv-webpack": "8.0.1",
"graphiql-explorer": "0.9.0",
"graphql": "16.6.0",
"graphql-request": "^5.0.0",
"lodash.debounce": "^4.0.8",
"markdown-it": "^13.0.1",
"graphql-request": "5.0.0",
"lodash.debounce": "4.0.8",
"markdown-it": "13.0.1",
"neo4j-driver": "5.1.0",
"prettier": "^2.7.1",
"prettier": "2.7.1",
"process": "0.11.10",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-resizable": "^3.0.4"
"react-resizable": "3.0.4"
},
"devDependencies": {
"@playwright/test": "1.27.1",
Expand Down
2 changes: 2 additions & 0 deletions packages/graphql-toolbox/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export const LOCAL_STATE_SHOW_LINT_MARKERS = "neo4j.graphql.showLintMarkers";
export const LOCAL_STATE_SELECTED_DATABASE_NAME = "neo4j.graphql.selectedDatabaseName";
export const LOCAL_STATE_HIDE_INTROSPECTION_PROMPT = "neo4j.graphql.hideIntrospectionPrompt";
export const LOCAL_STATE_GRID_STATE = "neo4j.graphql.gridState";
export const LOCAL_STATE_ENABLE_PRODUCT_USAGE_TRACKING = "neo4j.graphql.enableProductUsageTracking";
export const LOCAL_STATE_HIDE_PRODUCT_USAGE_MESSAGE = "neo4j.graphql.hideProductUsageMessage";

export const SCHEMA_EDITOR_INPUT = "SCHEMA_EDITOR_INPUT";
export const EDITOR_QUERY_INPUT = "EDITOR_QUERY_INPUT";
Expand Down
27 changes: 26 additions & 1 deletion packages/graphql-toolbox/src/contexts/appsettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,48 @@
*/

import React, { Dispatch, useState, SetStateAction, useEffect } from "react";
import { LOCAL_STATE_CONSTRAINT, LOCAL_STATE_SHOW_LINT_MARKERS } from "../constants";
import {
LOCAL_STATE_CONSTRAINT,
LOCAL_STATE_ENABLE_PRODUCT_USAGE_TRACKING,
LOCAL_STATE_HIDE_PRODUCT_USAGE_MESSAGE,
LOCAL_STATE_SHOW_LINT_MARKERS,
} from "../constants";
import { ConstraintState } from "../types";
import { Storage } from "../utils/storage";

export interface State {
showLintMarkers: boolean;
enableProductUsageTracking: boolean;
hideProductUsageMessage: boolean;
setShowLintMarkers: (v: boolean) => void;
setEnableProductUsageTracking: (v: boolean) => void;
setHideProductUsageMessage: (v: boolean) => void;
}

export const AppSettingsContext = React.createContext({} as State);

const _resolveValue = (localStateLabel: string, defaultValue: boolean): boolean => {
const storedState = Storage.retrieve(localStateLabel);
return storedState !== null ? storedState === "true" : defaultValue;
};

export function AppSettingsProvider(props: React.PropsWithChildren<any>) {
const [value, setValue]: [value: State | undefined, setValue: Dispatch<SetStateAction<State>>] = useState<State>({
showLintMarkers: Storage.retrieve(LOCAL_STATE_SHOW_LINT_MARKERS) === "true",
enableProductUsageTracking: _resolveValue(LOCAL_STATE_ENABLE_PRODUCT_USAGE_TRACKING, true),
hideProductUsageMessage: _resolveValue(LOCAL_STATE_HIDE_PRODUCT_USAGE_MESSAGE, false),
setShowLintMarkers: (nextState: boolean) => {
Storage.store(LOCAL_STATE_SHOW_LINT_MARKERS, String(nextState));
setValue((values) => ({ ...values, showLintMarkers: nextState }));
},
setEnableProductUsageTracking: (nextState: boolean) => {
Storage.store(LOCAL_STATE_ENABLE_PRODUCT_USAGE_TRACKING, String(nextState));
setValue((values) => ({ ...values, enableProductUsageTracking: nextState }));
},
setHideProductUsageMessage: (nextState: boolean) => {
Storage.store(LOCAL_STATE_HIDE_PRODUCT_USAGE_MESSAGE, String(nextState));
setValue((values) => ({ ...values, hideProductUsageMessage: nextState }));
},
});

useEffect(() => {
Expand Down
40 changes: 33 additions & 7 deletions packages/graphql-toolbox/src/modules/AppSettings/AppSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
import { useContext } from "react";
import { Checkbox, Radio } from "@neo4j-ndl/react";
import { Theme, ThemeContext } from "../../contexts/theme";
import { LOCAL_STATE_SHOW_LINT_MARKERS } from "../../constants";
import { Storage } from "../../utils/storage";
import { AppSettingsContext } from "../..//contexts/appsettings";

interface Props {
Expand All @@ -37,16 +35,20 @@ export const AppSettings = ({ onClickClose }: Props) => {
theme.setTheme(next);
};

const onChangeShowLintMarkersCheckbox = (): void => {
const onChangeShowLintMarkers = (): void => {
appSettings.setShowLintMarkers(!appSettings.showLintMarkers);
Storage.store(LOCAL_STATE_SHOW_LINT_MARKERS, Boolean(!appSettings.showLintMarkers).toString());
};

const onChangeProductUsageTracking = (): void => {
appSettings.setEnableProductUsageTracking(!appSettings.enableProductUsageTracking);
};

return (
<div className="p-6 w-full">
<div className="pb-6 flex justify-between items-center">
<span className="h5">Settings</span>
<span
data-test-settings-close-button
className="text-lg cursor-pointer"
onClick={onClickClose}
onKeyDown={onClickClose}
Expand All @@ -73,17 +75,41 @@ export const AppSettings = ({ onClickClose }: Props) => {
checked={theme.theme === Theme.DARK}
onChange={handleOnChangeEditorTheme}
/>
<div className="mt-4">
<div className="mt-3">
<Checkbox
data-test-show-lint-markers-checkbox
data-test-show-lint-markers
className="m-0"
aria-label="Show lint markers"
label="Show lint markers"
checked={appSettings.showLintMarkers}
onChange={onChangeShowLintMarkersCheckbox}
onChange={onChangeShowLintMarkers}
/>
</div>
</div>
</div>
<div className="pt-9">
<span className="h6">Product Analytics</span>
<div className="pt-3 flex">
<Checkbox
data-test-enable-product-usage-tracking
aria-label="Product usage tracking toggle"
className={`mt-1 ${
appSettings.enableProductUsageTracking
? "data-test-enable-product-usage-tracking-checked"
: ""
}`}
checked={appSettings.enableProductUsageTracking}
onChange={onChangeProductUsageTracking}
/>
<div className="ml-3">
<p className="text-sm">Product usage</p>
<p className="text-xs">
This data helps us prioritize features and improvements. No personal information is
collected or sent.
</p>
</div>
</div>
</div>
<div className="absolute bottom-2 right-28 font-bold text-xs flex flex-col">
<span>Made by Neo4j, Inc</span>
<span>Copyright &copy; 2002-2022</span>
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql-toolbox/src/modules/Login/FormInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ export const FormInput = (props: Props) => {
if (props.testtag) {
options[props.testtag] = true;
}
return <TextInput fluid {...props} {...options} />;
return <TextInput aria-label={props.name} fluid {...props} {...options} />;
};
12 changes: 11 additions & 1 deletion packages/graphql-toolbox/src/modules/Main/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,30 @@
* limitations under the License.
*/

import { useContext, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { GraphQLSchema } from "graphql";
import { TopBar } from "../TopBar/TopBar";
import { Login } from "../Login/Login";
import { SchemaView } from "../SchemaView/SchemaView";
import { Editor } from "../EditorView/Editor";
import { AuthContext } from "../../contexts/auth";
import { ScreenContext, Screen } from "../../contexts/screen";
import { invokeSegmentAnalytics } from "src/utils/segmentSnippet";

export const Main = () => {
const auth = useContext(AuthContext);
const screen = useContext(ScreenContext);
const [schema, setSchema] = useState<GraphQLSchema | undefined>(undefined);

useEffect(() => {
const segmentKey =
process.env.NODE_ENV === "production"
? process.env.SEGMENT_GRAPHQL_TOOLBOX_PROD_SOURCE
: process.env.SEGMENT_GRAPHQL_TOOLBOX_DEV_SOURCE;
if (!segmentKey) return;
invokeSegmentAnalytics(segmentKey);
}, []);

if (!auth.driver) {
return (
<div className="flex">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const SchemaSettings = ({
<div className="mb-1 flex items-baseline">
<Checkbox
className="m-0"
aria-label="Enable Regex"
label="Enable Regex"
checked={isRegexChecked === "true"}
onChange={onChangeRegexCheckbox}
Expand All @@ -106,6 +107,7 @@ export const SchemaSettings = ({
<Checkbox
data-test-schema-debug-checkbox
className="m-0"
aria-label="Enable Debug"
label="Enable Debug"
checked={isDebugChecked === "true"}
onChange={onChangeDebugCheckbox}
Expand Down
15 changes: 15 additions & 0 deletions packages/graphql-toolbox/src/modules/SchemaView/SchemaView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import { useCallback, useContext, useRef, useState } from "react";
import { Neo4jGraphQL } from "@neo4j/graphql";
import { toGraphQLTypeDefs } from "@neo4j/introspector";
import { Alert } from "@neo4j-ndl/react";
import { GraphQLError, GraphQLSchema } from "graphql";
import * as neo4j from "neo4j-driver";
import { EditorFromTextArea } from "codemirror";
Expand All @@ -34,6 +35,7 @@ import {
import { formatCode, ParserOptions } from "../EditorView/utils";
import { AuthContext } from "../../contexts/auth";
import { SettingsContext } from "../../contexts/settings";
import { AppSettingsContext } from "../../contexts/appsettings";
import { AppSettings } from "../AppSettings/AppSettings";
import { HelpDrawer } from "../HelpDrawer/HelpDrawer";
import { Storage } from "../../utils/storage";
Expand All @@ -44,6 +46,7 @@ import { SchemaEditor } from "./SchemaEditor";
import { ConstraintState, Favorite } from "../../types";
import { Favorites } from "./Favorites";
import { IntrospectionPrompt } from "./IntrospectionPrompt";
import { tracking } from "../../utils/tracking";

export interface Props {
hasSchema: boolean;
Expand All @@ -53,6 +56,7 @@ export interface Props {
export const SchemaView = ({ hasSchema, onChange }: Props) => {
const auth = useContext(AuthContext);
const settings = useContext(SettingsContext);
const appSettings = useContext(AppSettingsContext);
const [error, setError] = useState<string | GraphQLError>("");
const [showIntrospectionModal, setShowIntrospectionModal] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
Expand All @@ -78,6 +82,7 @@ export const SchemaView = ({ hasSchema, onChange }: Props) => {
];
setFavorites(newFavorites);
Storage.storeJSON(LOCAL_STATE_FAVORITES, newFavorites);
tracking.trackFavorites();
};

const setTypeDefsFromFavorite = (typeDefs: string) => {
Expand Down Expand Up @@ -213,6 +218,16 @@ export const SchemaView = ({ hasSchema, onChange }: Props) => {
introspect={introspect}
saveAsFavorite={saveAsFavorite}
/>
{!appSettings.hideProductUsageMessage ? (
<Alert
className="absolute bottom-7 ml-4 w-[57rem] z-40"
closeable
name="ProductUsageMessage"
title={<strong>Product analytics</strong>}
description="To help make the Neo4j GraphQL Toolbox better we collect data on product usage. Review your settings at any time."
onClose={() => appSettings.setHideProductUsageMessage(true)}
/>
) : null}
</div>
</div>
</div>
Expand Down
40 changes: 40 additions & 0 deletions packages/graphql-toolbox/src/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export {};

type Analytics = {
_writeKey: string;
_loadOptions: unknown;
SNIPPET_VERSION: string;
initialize: boolean;
invoked: boolean;
methods: string[];
factory: (e: string) => void;
push: (e: unknown) => void;
load: (key: string, e?: string | undefined) => void;
page: () => void;
track: (key: string, e: unknown) => void;
};

declare global {
interface Window {
analytics: Analytics;
}
}
Loading