mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Share: Add analytics to invite user flow (#99116)
This commit is contained in:
parent
c7edbffd82
commit
865e911e10
@ -170,6 +170,8 @@ export interface GrafanaConfig {
|
||||
externalUserMngLinkUrl: string;
|
||||
externalUserMngLinkName: string;
|
||||
externalUserMngInfo: string;
|
||||
externalUserMngAnalytics: boolean;
|
||||
externalUserMngAnalyticsParams: string;
|
||||
allowOrgCreate: boolean;
|
||||
disableLoginForm: boolean;
|
||||
defaultDatasource: string;
|
||||
|
@ -73,6 +73,8 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
externalUserMngLinkUrl = '';
|
||||
externalUserMngLinkName = '';
|
||||
externalUserMngInfo = '';
|
||||
externalUserMngAnalytics = false;
|
||||
externalUserMngAnalyticsParams = '';
|
||||
allowOrgCreate = false;
|
||||
feedbackLinksEnabled = true;
|
||||
disableLoginForm = false;
|
||||
|
@ -200,6 +200,8 @@ type FrontendSettingsDTO struct {
|
||||
ExternalUserMngInfo string `json:"externalUserMngInfo"`
|
||||
ExternalUserMngLinkUrl string `json:"externalUserMngLinkUrl"`
|
||||
ExternalUserMngLinkName string `json:"externalUserMngLinkName"`
|
||||
ExternalUserMngAnalytics bool `json:"externalUserMngAnalytics"`
|
||||
ExternalUserMngAnalyticsParams string `json:"externalUserMngAnalyticsParams"`
|
||||
ViewersCanEdit bool `json:"viewersCanEdit"`
|
||||
AngularSupportEnabled bool `json:"angularSupportEnabled"`
|
||||
EditorsCanAdmin bool `json:"editorsCanAdmin"`
|
||||
|
@ -225,6 +225,8 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
||||
ExternalUserMngInfo: hs.Cfg.ExternalUserMngInfo,
|
||||
ExternalUserMngLinkUrl: hs.Cfg.ExternalUserMngLinkUrl,
|
||||
ExternalUserMngLinkName: hs.Cfg.ExternalUserMngLinkName,
|
||||
ExternalUserMngAnalytics: hs.Cfg.ExternalUserMngAnalytics,
|
||||
ExternalUserMngAnalyticsParams: hs.Cfg.ExternalUserMngAnalyticsParams,
|
||||
ViewersCanEdit: hs.Cfg.ViewersCanEdit,
|
||||
AngularSupportEnabled: hs.Cfg.AngularSupportEnabled,
|
||||
EditorsCanAdmin: hs.Cfg.EditorsCanAdmin,
|
||||
|
@ -397,20 +397,22 @@ type Cfg struct {
|
||||
Quota QuotaSettings
|
||||
|
||||
// User settings
|
||||
AllowUserSignUp bool
|
||||
AllowUserOrgCreate bool
|
||||
VerifyEmailEnabled bool
|
||||
LoginHint string
|
||||
PasswordHint string
|
||||
DisableSignoutMenu bool
|
||||
ExternalUserMngLinkUrl string
|
||||
ExternalUserMngLinkName string
|
||||
ExternalUserMngInfo string
|
||||
AutoAssignOrg bool
|
||||
AutoAssignOrgId int
|
||||
AutoAssignOrgRole string
|
||||
LoginDefaultOrgId int64
|
||||
OAuthSkipOrgRoleUpdateSync bool
|
||||
AllowUserSignUp bool
|
||||
AllowUserOrgCreate bool
|
||||
VerifyEmailEnabled bool
|
||||
LoginHint string
|
||||
PasswordHint string
|
||||
DisableSignoutMenu bool
|
||||
ExternalUserMngLinkUrl string
|
||||
ExternalUserMngLinkName string
|
||||
ExternalUserMngInfo string
|
||||
ExternalUserMngAnalytics bool
|
||||
ExternalUserMngAnalyticsParams string
|
||||
AutoAssignOrg bool
|
||||
AutoAssignOrgId int
|
||||
AutoAssignOrgRole string
|
||||
LoginDefaultOrgId int64
|
||||
OAuthSkipOrgRoleUpdateSync bool
|
||||
|
||||
// ExpressionsEnabled specifies whether expressions are enabled.
|
||||
ExpressionsEnabled bool
|
||||
@ -1713,6 +1715,8 @@ func readUserSettings(iniFile *ini.File, cfg *Cfg) error {
|
||||
cfg.ExternalUserMngLinkUrl = valueAsString(users, "external_manage_link_url", "")
|
||||
cfg.ExternalUserMngLinkName = valueAsString(users, "external_manage_link_name", "")
|
||||
cfg.ExternalUserMngInfo = valueAsString(users, "external_manage_info", "")
|
||||
cfg.ExternalUserMngAnalytics = users.Key("external_manage_analytics").MustBool(false)
|
||||
cfg.ExternalUserMngAnalyticsParams = valueAsString(users, "external_manage_analytics_params", "")
|
||||
|
||||
cfg.ViewersCanEdit = users.Key("viewers_can_edit").MustBool(false)
|
||||
cfg.EditorsCanAdmin = users.Key("editors_can_admin").MustBool(false)
|
||||
|
@ -59,6 +59,34 @@ describe('ShareMenu', () => {
|
||||
expect(await screen.queryByTestId(selector.inviteUser)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render invite user with analytics when config is provided', async () => {
|
||||
Object.defineProperty(contextSrv, 'isSignedIn', {
|
||||
value: true,
|
||||
});
|
||||
grantUserPermissions([AccessControlAction.OrgUsersAdd]);
|
||||
|
||||
config.externalUserMngLinkUrl = 'http://localhost:3000/users';
|
||||
config.externalUserMngAnalytics = true;
|
||||
config.externalUserMngAnalyticsParams = 'src=grafananet&other=value1';
|
||||
setup({ meta: { canEdit: true } });
|
||||
|
||||
const inviteUser = await screen.findByTestId(selector.inviteUser);
|
||||
// Mock window.open
|
||||
const windowOpenMock = jest.spyOn(window, 'open').mockImplementation(() => null);
|
||||
|
||||
// Simulate click event
|
||||
inviteUser.click();
|
||||
|
||||
// Assert window.open was called with the correct URL
|
||||
expect(windowOpenMock).toHaveBeenCalledWith(
|
||||
'http://localhost:3000/users?src=grafananet&other=value1&cnt=share-invite',
|
||||
'_blank'
|
||||
);
|
||||
|
||||
// Restore the original implementation
|
||||
windowOpenMock.mockRestore();
|
||||
});
|
||||
|
||||
it('should not render invite user when externalUserMngLinkUrl is not provided', async () => {
|
||||
Object.defineProperty(contextSrv, 'isSignedIn', {
|
||||
value: true,
|
||||
|
@ -13,6 +13,7 @@ import { AccessControlAction } from 'app/types';
|
||||
|
||||
import { isPublicDashboardsEnabled } from '../../../dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
||||
import { getTrackingSource, shareDashboardType } from '../../../dashboard/components/ShareModal/utils';
|
||||
import { getExternalUserMngLinkUrl } from '../../../users/utils';
|
||||
import { DashboardScene } from '../../scene/DashboardScene';
|
||||
import { DashboardInteractions } from '../../utils/interactions';
|
||||
|
||||
@ -93,7 +94,9 @@ export default function ShareMenu({ dashboard, panel }: { dashboard: DashboardSc
|
||||
label: t('share-dashboard.menu.invite-user-title', 'Invite new member'),
|
||||
renderCondition: !!config.externalUserMngLinkUrl && contextSrv.hasPermission(AccessControlAction.OrgUsersAdd),
|
||||
onClick: () => {
|
||||
window.open(config.externalUserMngLinkUrl, '_blank');
|
||||
const url = getExternalUserMngLinkUrl('share-invite');
|
||||
|
||||
window.open(url.toString(), '_blank');
|
||||
},
|
||||
renderDividerAbove: true,
|
||||
component: () => <Icon name="external-link-alt" className={styles.inviteUserItemIcon} />,
|
||||
|
@ -25,6 +25,9 @@ const setup = (propOverrides?: object) => {
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
config.externalUserMngLinkUrl = props.externalUserMngLinkUrl;
|
||||
config.externalUserMngLinkName = props.externalUserMngLinkName;
|
||||
|
||||
const { rerender } = render(<UsersActionBarUnconnected {...props} />);
|
||||
|
||||
return { rerender, props };
|
||||
@ -53,11 +56,38 @@ describe('Render', () => {
|
||||
|
||||
it('should show external user management button', () => {
|
||||
setup({
|
||||
externalUserMngLinkUrl: 'some/url',
|
||||
externalUserMngLinkUrl: 'http://some/url',
|
||||
externalUserMngLinkName: 'someUrl',
|
||||
});
|
||||
|
||||
expect(screen.getByRole('link', { name: 'someUrl' })).toHaveAttribute('href', 'some/url');
|
||||
expect(screen.getByRole('link', { name: 'someUrl' })).toHaveAttribute('href', 'http://some/url');
|
||||
});
|
||||
|
||||
it('should show external user management button with analytics values when configured', () => {
|
||||
config.externalUserMngAnalytics = true;
|
||||
config.externalUserMngAnalyticsParams = 'src=grafananet&other=value1';
|
||||
|
||||
setup({
|
||||
externalUserMngLinkUrl: 'http://some/url',
|
||||
externalUserMngLinkName: 'someUrl',
|
||||
});
|
||||
|
||||
expect(screen.getByRole('link', { name: 'someUrl' })).toHaveAttribute(
|
||||
'href',
|
||||
'http://some/url?src=grafananet&other=value1&cnt=manage-users'
|
||||
);
|
||||
});
|
||||
|
||||
it('should show external user management button without analytics values when disabled', () => {
|
||||
config.externalUserMngAnalytics = false;
|
||||
config.externalUserMngAnalyticsParams = 'src=grafananet&other=value1';
|
||||
|
||||
setup({
|
||||
externalUserMngLinkUrl: 'http://some/url',
|
||||
externalUserMngLinkName: 'someUrl',
|
||||
});
|
||||
|
||||
expect(screen.getByRole('link', { name: 'someUrl' })).toHaveAttribute('href', 'http://some/url');
|
||||
});
|
||||
|
||||
it('should not show invite button when externalUserMngInfo is set and disableLoginForm is true', () => {
|
||||
|
@ -9,6 +9,7 @@ import { selectTotal } from '../invites/state/selectors';
|
||||
|
||||
import { changeSearchQuery } from './state/actions';
|
||||
import { getUsersSearchQuery } from './state/selectors';
|
||||
import { getExternalUserMngLinkUrl } from './utils';
|
||||
|
||||
export interface OwnProps {
|
||||
showInvites: boolean;
|
||||
@ -67,7 +68,7 @@ export const UsersActionBarUnconnected = ({
|
||||
)}
|
||||
{showInviteButton && <LinkButton href="org/users/invite">Invite</LinkButton>}
|
||||
{externalUserMngLinkUrl && (
|
||||
<LinkButton href={externalUserMngLinkUrl} target="_blank" rel="noopener">
|
||||
<LinkButton href={getExternalUserMngLinkUrl('manage-users')} target="_blank" rel="noopener">
|
||||
{externalUserMngLinkName}
|
||||
</LinkButton>
|
||||
)}
|
||||
|
21
public/app/features/users/utils.ts
Normal file
21
public/app/features/users/utils.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
export function getExternalUserMngLinkUrl(cnt: string) {
|
||||
const url = new URL(config.externalUserMngLinkUrl);
|
||||
|
||||
if (config.externalUserMngAnalytics) {
|
||||
// Add query parameters in config.externalUserMngAnalyticsParams to track conversion
|
||||
if (!!config.externalUserMngAnalyticsParams) {
|
||||
const params = config.externalUserMngAnalyticsParams.split('&');
|
||||
params.forEach((param) => {
|
||||
const [key, value] = param.split('=');
|
||||
url.searchParams.append(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
// Add specific CTA cnt to track conversion
|
||||
url.searchParams.append('cnt', cnt);
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
Loading…
Reference in New Issue
Block a user