* start moving ui to redux tlk * remove unused reducer * changes for gdpr and site types * ui: migrating duck/roles to mobx * ui: drop unreferenced types * ui: drop unreferenced types * ui: move player slice reducer to mobx family * ui: move assignments to issueReportingStore.ts * remove issues store * some fixes after issues store * remove errors reducer, drop old components * finish removing errors reducer * start moving integrations state to mobx * change(ui): funnel duck cleanup * change(ui): custom fields * change(ui): customMetrics cleanup * change(ui): customMetrics cleanup * change(ui): duck/filters minor cleanup * change(ui): duck/filters cleanup * change(ui): duck/customMetrics cleanup and upgrades * fix integrations service, fix babel config to >.25 + not ie * refactoring integrations reducers etc WIP * finish removing integrations state * some fixes for integrated check * start of projects refactoring * move api and "few" files to new project store * new batch for site -> projects * fix setid context * move all critical components, drop site duck * remove all duck/site refs, remove old components * fixup for SessionTags.tsx, remove duck/sources (?) * move session store * init sessionstore outside of context * fix userfilter * replace simple actions for session store * sessions sotre * Rtm temp (#2597) * change(ui): duck/search wip * change(ui): duck/search wip * change(ui): duck/search wip * change(ui): duck/searchLive wip * change(ui): duck/searchLive wip * change(ui): duck/searchLive wip * change(ui): duck/searchLive wip * change(ui): search states * change(ui): search states * change(ui): search states * change(ui): fix savedSearch store * change(ui): fix savedSearch store * some fixes for session connector * change(ui): fix savedSearch store * change(ui): fix searchLive * change(ui): fix searchLive * fixes for session replay * change(ui): bookmark fetch * last components for sessions * add fetchautoplaylist * finish session reducer, remove deleted reducers * change(ui): fix the search fetch * change(ui): fix the search fetch * fix integrations call ctx * ensure ctx for sessionstore * fix(ui): checking for latest sessions path * start removing user reducer * removing user reducer pt2... * finish user store * remove rand log * fix crashes * tinkering workflow file for tracker test * making sure prefetched sessions work properly * fix conflict * fix router redirects during loading --------- Co-authored-by: Shekar Siri <sshekarsiri@gmail.com>
186 lines
6.5 KiB
TypeScript
186 lines
6.5 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Tag } from 'antd';
|
|
import cn from 'classnames';
|
|
import { Loader, Button, TextLink, NoContent, Pagination, PageTitle, Divider, Icon } from 'UI';
|
|
import withPageTitle from 'HOCs/withPageTitle';
|
|
import stl from './sites.module.css';
|
|
import NewSiteForm from './NewSiteForm';
|
|
import SiteSearch from './SiteSearch';
|
|
import AddProjectButton from './AddProjectButton';
|
|
import InstallButton from './InstallButton';
|
|
import ProjectKey from './ProjectKey';
|
|
import { sliceListPerPage } from 'App/utils';
|
|
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
|
import { useModal } from 'App/components/Modal';
|
|
import CaptureRate from 'Shared/SessionSettings/components/CaptureRate';
|
|
import { BranchesOutlined } from '@ant-design/icons';
|
|
import { observer } from 'mobx-react-lite';
|
|
import { useStore } from 'App/mstore'
|
|
|
|
type Project = {
|
|
id: string;
|
|
name: string;
|
|
conditionsCount: number;
|
|
platform: 'web' | 'mobile';
|
|
host: string;
|
|
projectKey: string;
|
|
sampleRate: number;
|
|
};
|
|
|
|
const Sites = () => {
|
|
const { projectsStore, userStore } = useStore();
|
|
const user = userStore.account;
|
|
const sites = projectsStore.list;
|
|
const loading = projectsStore.sitesLoading;
|
|
const init = projectsStore.initProject
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [showCaptureRate, setShowCaptureRate] = useState(true);
|
|
const [activeProject, setActiveProject] = useState<Project | null>(null);
|
|
const [page, setPage] = useState(1);
|
|
const pageSize: number = 10;
|
|
|
|
const isAdmin = user.admin || user.superAdmin;
|
|
const filteredSites = sites.filter((site: { name: string }) =>
|
|
site.name.toLowerCase().includes(searchQuery.toLowerCase())
|
|
);
|
|
|
|
const { showModal, hideModal } = useModal();
|
|
|
|
const EditButton = ({ isAdmin, onClick }: { isAdmin: boolean; onClick: () => void }) => {
|
|
const _onClick = () => {
|
|
onClick();
|
|
showModal(<NewSiteForm onClose={hideModal} />, { right: true });
|
|
};
|
|
|
|
return <Button icon="edit" variant="text-primary" disabled={!isAdmin} onClick={_onClick} />;
|
|
};
|
|
|
|
const captureRateClickHandler = (project: Project) => {
|
|
setActiveProject(project);
|
|
setShowCaptureRate(true);
|
|
};
|
|
|
|
const updatePage = (page: number) => {
|
|
setPage(page);
|
|
};
|
|
|
|
const ProjectItem = ({ project }: { project: Project }) => (
|
|
<div
|
|
key={project.id}
|
|
className="grid grid-cols-12 gap-2 w-full group hover:bg-active-blue items-center px-5 py-3"
|
|
>
|
|
<div className="col-span-4">
|
|
<div className="flex items-center">
|
|
<div className="relative flex items-center justify-center w-10 h-10 rounded-full bg-tealx-light">
|
|
<Icon
|
|
color={'tealx'}
|
|
size={18}
|
|
name={project.platform === 'web' ? 'browser/browser' : 'mobile'}
|
|
/>
|
|
</div>
|
|
<span className="ml-2">{project.host}</span>
|
|
<div className={'ml-4 flex items-center gap-2'}>
|
|
{project.platform === 'web' ? null : <Tag bordered={false} color="green">MOBILE BETA</Tag>}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="col-span-3">
|
|
<ProjectKey value={project.projectKey} tooltip="Project key copied to clipboard" />
|
|
</div>
|
|
<div className="col-span-3 flex items-center">
|
|
<Button variant="text-primary" onClick={() => captureRateClickHandler(project)}>
|
|
{project.sampleRate}%
|
|
</Button>
|
|
{project.conditionsCount > 0 ? (
|
|
<Button
|
|
variant="text-primary"
|
|
onClick={() => captureRateClickHandler(project)}
|
|
className="ml-2"
|
|
>
|
|
<BranchesOutlined rotate={90} /> {project.conditionsCount} Conditions
|
|
</Button>
|
|
) : null}
|
|
</div>
|
|
<div className="col-span-2 justify-self-end flex items-center">
|
|
<div className="mr-4">
|
|
<InstallButton site={project} />
|
|
</div>
|
|
<div className="invisible group-hover:visible">
|
|
<EditButton isAdmin={isAdmin} onClick={() => init(project)} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<Loader loading={loading}>
|
|
<div className="bg-white rounded-lg shadow-sm border">
|
|
<div className={cn(stl.tabHeader, 'px-5 pt-5')}>
|
|
<PageTitle
|
|
title={<div className="mr-4">Projects</div>}
|
|
actionButton={
|
|
<TextLink
|
|
icon="book"
|
|
href="https://docs.openreplay.com/installation"
|
|
label="Installation Docs"
|
|
/>
|
|
}
|
|
/>
|
|
|
|
<div className="flex ml-auto items-center">
|
|
<AddProjectButton isAdmin={isAdmin} />
|
|
<div className="mx-2" />
|
|
<SiteSearch onChange={(value) => setSearchQuery(value)} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className={stl.list}>
|
|
<NoContent
|
|
title={
|
|
<div className="flex flex-col items-center justify-center">
|
|
<AnimatedSVG name={ICONS.NO_PROJECTS} size={60} />
|
|
<div className="text-center text-gray-600 my-4">No matching results</div>
|
|
</div>
|
|
}
|
|
size="small"
|
|
show={!loading && filteredSites.length === 0}
|
|
>
|
|
<div className="grid grid-cols-12 gap-2 w-full items-center px-5 py-3 font-medium">
|
|
<div className="col-span-4">Project Name</div>
|
|
<div className="col-span-3">Key</div>
|
|
<div className="col-span-2">Capture Rate</div>
|
|
<div className="col-span-3"></div>
|
|
</div>
|
|
<Divider className="m-0" />
|
|
|
|
{sliceListPerPage(filteredSites, page - 1, pageSize).map((project: Project) => (
|
|
<React.Fragment key={project.id}>
|
|
<ProjectItem project={project} />
|
|
<Divider className="m-0" />
|
|
</React.Fragment>
|
|
))}
|
|
|
|
<div className="w-full flex items-center justify-center py-10">
|
|
<Pagination
|
|
page={page}
|
|
total={filteredSites.length}
|
|
onPageChange={(page) => updatePage(page)}
|
|
limit={pageSize}
|
|
/>
|
|
</div>
|
|
</NoContent>
|
|
</div>
|
|
</div>
|
|
|
|
<CaptureRate
|
|
setShowCaptureRate={setShowCaptureRate}
|
|
showCaptureRate={showCaptureRate}
|
|
projectId={activeProject?.id}
|
|
isMobile={activeProject?.platform !== 'web'}
|
|
open={showCaptureRate && !!activeProject}
|
|
/>
|
|
</Loader>
|
|
);
|
|
};
|
|
|
|
export default withPageTitle('Projects - OpenReplay Preferences')(observer(Sites));
|