Skip to content

Commit 7650483

Browse files
committed
bootstrap new feedback integration system
1 parent 7901d32 commit 7650483

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2229
-0
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
"packages/feedback",
5959
"packages/feedback-async",
6060
"packages/feedback-screenshot",
61+
"packages/feedback2",
62+
"packages/feedback2-modal",
63+
"packages/feedback2-screenshot",
6164
"packages/gatsby",
6265
"packages/integrations",
6366
"packages/integration-shims",
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
build/

packages/feedback2-modal/.eslintrc.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Note: All paths are relative to the directory in which eslint is being run, rather than the directory where this file
2+
// lives
3+
4+
// ESLint config docs: https://eslint.org/docs/user-guide/configuring/
5+
6+
module.exports = {
7+
extends: ['../../.eslintrc.js'],
8+
overrides: [
9+
{
10+
files: ['src/**/*.ts', 'src/**/*.tsx'],
11+
rules: {
12+
'@sentry-internal/sdk/no-unsupported-es6-methods': 'off',
13+
},
14+
},
15+
{
16+
files: ['jest.setup.ts', 'jest.config.ts'],
17+
parserOptions: {
18+
project: ['tsconfig.test.json'],
19+
},
20+
rules: {
21+
'no-console': 'off',
22+
},
23+
},
24+
],
25+
};

packages/feedback2-modal/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
/*.tgz
3+
.eslintcache
4+
build

packages/feedback2-modal/LICENSE

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Copyright (c) 2023 Sentry (https://sentry.io) and individual contributors. All rights reserved.
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
5+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
6+
persons to whom the Software is furnished to do so, subject to the following conditions:
7+
8+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
9+
Software.
10+
11+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
12+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
13+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
14+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

packages/feedback2-modal/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<p align="center">
2+
<a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
3+
<img src="https://sentry-brand.storage.googleapis.com/sentry-wordmark-dark-280x84.png" alt="Sentry" width="280" height="84">
4+
</a>
5+
</p>
6+
7+
# Sentry Integration for Feedback
8+
9+
This SDK is **considered experimental and in a beta state**. It may experience breaking changes, and may be discontinued at any time. Please reach out on
10+
[GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback/concerns.
11+
12+
To view Feedback in Sentry, your [Sentry organization must be an early adopter](https://docs.sentry.io/product/accounts/early-adopter-features/).
13+
14+
## Installation
15+
16+
Please read the [offical integration documentation](https://docs.sentry.io/platforms/javascript/user-feedback/) for installation instructions.
17+
18+
## Configuration
19+
20+
The Feedback integration is highly customizable, please read the [official integration documentation](https://docs.sentry.io/platforms/javascript/user-feedback/configuration/) for the most up-to-date configuration options.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const baseConfig = require('../../jest/jest.config.js');
2+
3+
module.exports = {
4+
...baseConfig,
5+
testEnvironment: 'jsdom',
6+
};

packages/feedback2-modal/package.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"name": "@sentry-internal/feedback2-modal",
3+
"version": "7.100.0",
4+
"description": "Sentry SDK integration for user feedback",
5+
"repository": "git://github.com/getsentry/sentry-javascript.git",
6+
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/feedback",
7+
"author": "Sentry",
8+
"license": "MIT",
9+
"engines": {
10+
"node": ">=12"
11+
},
12+
"files": [
13+
"cjs",
14+
"esm",
15+
"types",
16+
"types-ts3.8"
17+
],
18+
"main": "build/npm/cjs/index.js",
19+
"module": "build/npm/esm/index.js",
20+
"types": "build/npm/types/index.d.ts",
21+
"typesVersions": {
22+
"<4.9": {
23+
"build/npm/types/index.d.ts": [
24+
"build/npm/types-ts3.8/index.d.ts"
25+
]
26+
}
27+
},
28+
"publishConfig": {
29+
"access": "public"
30+
},
31+
"dependencies": {
32+
"@sentry-internal/feedback-screenshot": "7.100.0",
33+
"@sentry/core": "7.100.0",
34+
"@sentry/types": "7.100.0",
35+
"@sentry/utils": "7.100.0",
36+
"preact": "^10.19.4",
37+
"preact-compat": "^3.19.0"
38+
},
39+
"scripts": {
40+
"build": "run-p build:transpile build:types build:bundle",
41+
"build:transpile": "rollup -c rollup.npm.config.mjs",
42+
"build:bundle": "rollup -c rollup.bundle.config.mjs",
43+
"build:dev": "run-p build:transpile build:types",
44+
"build:types": "run-s build:types:core build:types:downlevel",
45+
"build:types:core": "tsc -p tsconfig.types.json",
46+
"build:types:downlevel": "yarn downlevel-dts build/npm/types build/npm/types-ts3.8 --to ts3.8",
47+
"build:watch": "run-p build:transpile:watch build:bundle:watch build:types:watch",
48+
"build:dev:watch": "run-p build:transpile:watch build:types:watch",
49+
"build:transpile:watch": "yarn build:transpile --watch",
50+
"build:bundle:watch": "yarn build:bundle --watch",
51+
"build:types:watch": "tsc -p tsconfig.types.json --watch",
52+
"build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm",
53+
"circularDepCheck": "madge --circular src/index.ts",
54+
"clean": "rimraf build sentry-feedback-*.tgz",
55+
"fix": "eslint . --format stylish --fix",
56+
"lint": "eslint . --format stylish",
57+
"test": "jest",
58+
"test:watch": "jest --watch",
59+
"yalc:publish": "ts-node ../../scripts/prepack.ts --bundles && yalc publish ./build/npm --push --sig"
60+
},
61+
"volta": {
62+
"extends": "../../package.json"
63+
},
64+
"sideEffects": false
65+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { makeBaseBundleConfig, makeBundleConfigVariants } from '@sentry-internal/rollup-utils';
2+
3+
export default makeBundleConfigVariants(
4+
makeBaseBundleConfig({
5+
bundleType: 'addon',
6+
entrypoints: ['src/index.ts'],
7+
jsVersion: 'es6',
8+
licenseTitle: '@sentry-internal/feedback-async',
9+
outputFileBase: () => 'bundles/feedback-async',
10+
sucrase: {
11+
jsxPragma: 'h',
12+
jsxFragmentPragma: 'Fragment',
13+
},
14+
}),
15+
);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils';
2+
3+
export default makeNPMConfigVariants(
4+
makeBaseNPMConfig({
5+
hasBundles: true,
6+
packageSpecificConfig: {
7+
output: {
8+
// set exports to 'named' or 'auto' so that rollup doesn't warn
9+
exports: 'named',
10+
// set preserveModules to false because for feedback we actually want
11+
// to bundle everything into one file.
12+
preserveModules: false,
13+
},
14+
},
15+
}),
16+
);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// biome-ignore lint/nursery/noUnusedImports: reason
2+
import { h } from 'preact'; // eslint-disable-line @typescript-eslint/no-unused-vars
3+
import type { ComponentChildren, VNode } from 'preact';
4+
import { useMemo } from 'preact/hooks';
5+
6+
import { Form } from './Form';
7+
import type { Props as FormProps } from './Form';
8+
import type { Props as LogoProps } from './Logo';
9+
import { Logo } from './Logo';
10+
11+
export interface Props extends FormProps, LogoProps {
12+
children?: ComponentChildren;
13+
formTitle: string;
14+
showBranding: boolean;
15+
}
16+
17+
export function DialogContent({ children, colorScheme, formTitle, showBranding, ...props }: Props): VNode {
18+
const logoHtml = useMemo(() => {
19+
const logo = Logo({ colorScheme });
20+
return { __html: logo.outerHTML };
21+
}, [colorScheme]);
22+
23+
return (
24+
<div
25+
class="dialog__content"
26+
onClick={e => {
27+
// Stop event propagation so clicks on content modal do not propagate to dialog (which will close dialog)
28+
e.stopPropagation();
29+
}}
30+
>
31+
<h2 class="dialog__header">
32+
{formTitle}
33+
{showBranding ? (
34+
<a
35+
class="brand-link"
36+
target="_blank"
37+
href="https://sentry.io/welcome/"
38+
title="Powered by Sentry"
39+
rel="noopener noreferrer"
40+
dangerouslySetInnerHTML={logoHtml}
41+
/>
42+
) : null}
43+
</h2>
44+
<div style={{ display: 'flex', flexDirection: 'row', gap: '8px' }}>
45+
{children}
46+
<Form {...props} />
47+
</div>
48+
</div>
49+
);
50+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// biome-ignore lint/nursery/noUnusedImports: reason
2+
import { h } from 'preact'; // eslint-disable-line @typescript-eslint/no-unused-vars
3+
import type { VNode } from 'preact';
4+
import type { FeedbackFormData } from '../types';
5+
6+
export interface Props {
7+
cancelButtonLabel: string;
8+
defaultEmail: string;
9+
defaultName: string;
10+
emailLabel: string;
11+
emailPlaceholder: string;
12+
errorMessage?: string | undefined;
13+
isEmailRequired: boolean;
14+
isNameRequired: boolean;
15+
messageLabel: string;
16+
messagePlaceholder: string;
17+
nameLabel: string;
18+
namePlaceholder: string;
19+
onCancel: (e: Event) => void;
20+
onSubmit: (feedback: FeedbackFormData) => void;
21+
showEmail: boolean;
22+
showName: boolean;
23+
submitButtonLabel: string;
24+
}
25+
26+
function retrieveStringValue(formData: FormData, key: string): string {
27+
const value = formData.get(key);
28+
if (typeof value === 'string') {
29+
return value.trim();
30+
}
31+
return '';
32+
}
33+
34+
export function Form({
35+
cancelButtonLabel,
36+
defaultEmail,
37+
defaultName,
38+
emailLabel,
39+
emailPlaceholder,
40+
errorMessage,
41+
isEmailRequired,
42+
isNameRequired,
43+
messageLabel,
44+
messagePlaceholder,
45+
nameLabel,
46+
namePlaceholder,
47+
onCancel,
48+
onSubmit,
49+
showEmail,
50+
showName,
51+
submitButtonLabel,
52+
}: Props): VNode {
53+
return (
54+
<form
55+
class="form"
56+
onSubmit={e => {
57+
e.preventDefault();
58+
if (!(e.target instanceof HTMLFormElement)) {
59+
return;
60+
}
61+
try {
62+
const formData = new FormData(e.target);
63+
onSubmit({
64+
name: retrieveStringValue(formData, 'name'),
65+
email: retrieveStringValue(formData, 'email'),
66+
message: retrieveStringValue(formData, 'message'),
67+
});
68+
} catch {
69+
// pass
70+
}
71+
}}
72+
>
73+
{errorMessage ? <div class="form__error-container">{errorMessage}</div> : null}
74+
75+
{showName ? (
76+
<label for="name" class="form__label">
77+
<LabelText label={nameLabel} isRequired={isNameRequired} />
78+
<input
79+
class="form__input"
80+
defaultValue={defaultName}
81+
id="name"
82+
name="name"
83+
placeholder={namePlaceholder}
84+
required={isNameRequired}
85+
type="text"
86+
/>
87+
</label>
88+
) : (
89+
<input aria-hidden value={defaultName} name="name" type="hidden" />
90+
)}
91+
92+
{showEmail ? (
93+
<label for="email" class="form__label">
94+
<LabelText label={emailLabel} isRequired={isEmailRequired} />
95+
<input
96+
class="form__input"
97+
defaultValue={defaultEmail}
98+
id="email"
99+
name="email"
100+
placeholder={emailPlaceholder}
101+
required={isEmailRequired}
102+
type="text"
103+
></input>
104+
</label>
105+
) : (
106+
<input aria-hidden value={defaultEmail} name="email" type="hidden" />
107+
)}
108+
109+
<label for="message" class="form__label">
110+
<LabelText label={messageLabel} isRequired />
111+
<textarea
112+
autoFocus
113+
class="form__input form__input--textarea"
114+
id="message"
115+
name="message"
116+
placeholder={messagePlaceholder}
117+
required={true}
118+
rows={5}
119+
/>
120+
</label>
121+
122+
<div class="btn-group">
123+
<button class="btn btn--primary" type="submit">
124+
{submitButtonLabel}
125+
</button>
126+
<button class="btn btn--default" type="button" onClick={onCancel}>
127+
{cancelButtonLabel}
128+
</button>
129+
</div>
130+
</form>
131+
);
132+
}
133+
134+
function LabelText({ label, isRequired }: { label: string; isRequired: boolean }): VNode {
135+
return (
136+
<span class="form__label__text">
137+
{label}
138+
{isRequired && <span class="form__label__text--required">(required)</span>}
139+
</span>
140+
);
141+
}

0 commit comments

Comments
 (0)