diff --git a/ee/session_recordings/session_recording_playlist.py b/ee/session_recordings/session_recording_playlist.py
index 7b1a962b187b7..c95c274e893dc 100644
--- a/ee/session_recordings/session_recording_playlist.py
+++ b/ee/session_recordings/session_recording_playlist.py
@@ -7,17 +7,14 @@
from loginas.utils import is_impersonated_session
from rest_framework import request, response, serializers, viewsets
from posthog.api.utils import action
-from rest_framework.exceptions import PermissionDenied
from posthog.api.forbid_destroy_model import ForbidDestroyModel
from posthog.api.routing import TeamAndOrgViewSetMixin
from posthog.api.shared import UserBasicSerializer
-from posthog.constants import AvailableFeature
from posthog.models import (
SessionRecording,
SessionRecordingPlaylist,
SessionRecordingPlaylistItem,
- Team,
User,
)
from posthog.models.activity_logging.activity_log import (
@@ -26,7 +23,6 @@
changes_between,
log_activity,
)
-from posthog.models.team.team import check_is_feature_available_for_team
from posthog.models.utils import UUIDT
from posthog.rate_limit import (
ClickHouseBurstRateThrottle,
@@ -108,8 +104,6 @@ def create(self, validated_data: dict, *args, **kwargs) -> SessionRecordingPlayl
request = self.context["request"]
team = self.context["get_team"]()
- self._check_can_create_playlist(team)
-
created_by = validated_data.pop("created_by", request.user)
playlist = SessionRecordingPlaylist.objects.create(
team=team,
@@ -158,12 +152,6 @@ def update(self, instance: SessionRecordingPlaylist, validated_data: dict, **kwa
return updated_playlist
- def _check_can_create_playlist(self, team: Team) -> bool:
- playlist_count = SessionRecordingPlaylist.objects.filter(deleted=False, team=team).count()
- if not check_is_feature_available_for_team(team.pk, AvailableFeature.RECORDINGS_PLAYLISTS, playlist_count):
- raise PermissionDenied("You have hit the limit for playlists for this team.")
- return True
-
class SessionRecordingPlaylistViewSet(TeamAndOrgViewSetMixin, ForbidDestroyModel, viewsets.ModelViewSet):
scope_object = "session_recording_playlist"
diff --git a/ee/session_recordings/test/test_session_recording_playlist.py b/ee/session_recordings/test/test_session_recording_playlist.py
index 0ec14e0decb15..2d26d96aab928 100644
--- a/ee/session_recordings/test/test_session_recording_playlist.py
+++ b/ee/session_recordings/test/test_session_recording_playlist.py
@@ -10,7 +10,6 @@
from rest_framework import status
from ee.api.test.base import APILicensedTest
-from ee.api.test.fixtures.available_product_features import AVAILABLE_PRODUCT_FEATURES
from posthog.models import SessionRecording, SessionRecordingPlaylistItem
from posthog.models.user import User
from posthog.session_recordings.models.session_recording_playlist import (
@@ -77,24 +76,13 @@ def test_creates_playlist(self):
"last_modified_by": response.json()["last_modified_by"],
}
- def test_creates_too_many_playlists(self):
- limit = 0
- self.organization.available_product_features = AVAILABLE_PRODUCT_FEATURES
- self.organization.save()
- for feature in AVAILABLE_PRODUCT_FEATURES:
- if "key" in feature and feature["key"] == "recordings_playlists":
- limit = int(feature["limit"])
- for _ in range(limit):
+ def test_can_create_many_playlists(self):
+ for i in range(100):
response = self.client.post(
f"/api/projects/{self.team.id}/session_recording_playlists",
- data={"name": "test"},
+ data={"name": f"test-{i}"},
)
assert response.status_code == status.HTTP_201_CREATED
- response = self.client.post(
- f"/api/projects/{self.team.id}/session_recording_playlists",
- data={"name": "test"},
- )
- assert response.status_code == status.HTTP_403_FORBIDDEN
def test_gets_individual_playlist_by_shortid(self):
create_response = self.client.post(f"/api/projects/{self.team.id}/session_recording_playlists")
diff --git a/frontend/__snapshots__/components-pay-gate-mini--pay-gate-mini-limit-feature-other--dark.png b/frontend/__snapshots__/components-pay-gate-mini--pay-gate-mini-limit-feature-other--dark.png
index 31efe3fd3cace..8bde340c5c487 100644
Binary files a/frontend/__snapshots__/components-pay-gate-mini--pay-gate-mini-limit-feature-other--dark.png and b/frontend/__snapshots__/components-pay-gate-mini--pay-gate-mini-limit-feature-other--dark.png differ
diff --git a/frontend/__snapshots__/components-pay-gate-mini--pay-gate-mini-limit-feature-other--light.png b/frontend/__snapshots__/components-pay-gate-mini--pay-gate-mini-limit-feature-other--light.png
index d59c88dfedec4..f2e2030e5ea36 100644
Binary files a/frontend/__snapshots__/components-pay-gate-mini--pay-gate-mini-limit-feature-other--light.png and b/frontend/__snapshots__/components-pay-gate-mini--pay-gate-mini-limit-feature-other--light.png differ
diff --git a/frontend/src/lib/components/PayGateMini/PayGateMini.stories.tsx b/frontend/src/lib/components/PayGateMini/PayGateMini.stories.tsx
index 94c76b9d303a4..120ce98c85b42 100644
--- a/frontend/src/lib/components/PayGateMini/PayGateMini.stories.tsx
+++ b/frontend/src/lib/components/PayGateMini/PayGateMini.stories.tsx
@@ -83,8 +83,8 @@ export const PayGateMiniLimitFeatureOther = (): JSX.Element => {
...meCurrent.organization,
available_product_features: [
{
- key: 'recordings_playlists',
- name: 'Recordings Playlists',
+ key: 'advanced_permissions',
+ name: 'Advanced Permissions',
limit: 3,
},
],
@@ -93,7 +93,7 @@ export const PayGateMiniLimitFeatureOther = (): JSX.Element => {
],
},
})
- return
+ return
}
export const PayGateMiniLimitFeatureProjects = (): JSX.Element => {
diff --git a/frontend/src/mocks/fixtures/_billing.tsx b/frontend/src/mocks/fixtures/_billing.tsx
index 276026beb55aa..3fea00a4a8e23 100644
--- a/frontend/src/mocks/fixtures/_billing.tsx
+++ b/frontend/src/mocks/fixtures/_billing.tsx
@@ -922,15 +922,6 @@ export const billingJson: BillingType = {
limit: null,
note: null,
},
- {
- key: 'recordings_playlists',
- name: 'Recording playlists',
- description:
- 'Create playlists of certain session recordings to easily find and watch them again in the future.',
- unit: 'playlists',
- limit: 5,
- note: null,
- },
{
key: 'session_replay_data_retention',
name: 'Data retention',
@@ -1041,15 +1032,6 @@ export const billingJson: BillingType = {
limit: null,
note: null,
},
- {
- key: 'recordings_playlists',
- name: 'Recording playlists',
- description:
- 'Create playlists of certain session recordings to easily find and watch them again in the future.',
- unit: null,
- limit: null,
- note: null,
- },
{
key: 'recordings_performance',
name: 'Network performance on recordings',
@@ -1293,15 +1275,6 @@ export const billingJson: BillingType = {
contact_support: false,
inclusion_only: false,
features: [
- {
- key: 'recordings_playlists',
- name: 'Recording playlists',
- description:
- 'Create playlists of certain session recordings to easily find and watch them again in the future.',
- images: null,
- icon_key: null,
- type: null,
- },
{
key: 'session_replay_data_retention',
name: 'Data retention',
diff --git a/frontend/src/scenes/session-recordings/SessionRecordings.tsx b/frontend/src/scenes/session-recordings/SessionRecordings.tsx
index 544b8fcd9ef1b..caa7a153d8943 100644
--- a/frontend/src/scenes/session-recordings/SessionRecordings.tsx
+++ b/frontend/src/scenes/session-recordings/SessionRecordings.tsx
@@ -8,7 +8,6 @@ import {
defaultAuthorizedUrlProperties,
} from 'lib/components/AuthorizedUrlList/authorizedUrlListLogic'
import { PageHeader } from 'lib/components/PageHeader'
-import { upgradeModalLogic } from 'lib/components/UpgradeModal/upgradeModalLogic'
import { VersionCheckerBanner } from 'lib/components/VersionChecker/VersionCheckerBanner'
import { FEATURE_FLAGS } from 'lib/constants'
import { useAsyncHandler } from 'lib/hooks/useAsyncHandler'
@@ -24,19 +23,15 @@ import { teamLogic } from 'scenes/teamLogic'
import { urls } from 'scenes/urls'
import { sidePanelSettingsLogic } from '~/layout/navigation-3000/sidepanel/panels/sidePanelSettingsLogic'
-import { AvailableFeature, NotebookNodeType, ReplayTabs } from '~/types'
+import { NotebookNodeType, ReplayTabs } from '~/types'
import { createPlaylist } from './playlist/playlistUtils'
import { SessionRecordingsPlaylist } from './playlist/SessionRecordingsPlaylist'
import { SavedSessionRecordingPlaylists } from './saved-playlists/SavedSessionRecordingPlaylists'
-import { savedSessionRecordingPlaylistsLogic } from './saved-playlists/savedSessionRecordingPlaylistsLogic'
import { humanFriendlyTabName, sessionReplaySceneLogic } from './sessionReplaySceneLogic'
import SessionRecordingTemplates from './templates/SessionRecordingTemplates'
function Header(): JSX.Element {
- const { guardAvailableFeature } = useValues(upgradeModalLogic)
- const playlistsLogic = savedSessionRecordingPlaylistsLogic({ tab: ReplayTabs.Home })
- const { playlists } = useValues(playlistsLogic)
const { tab } = useValues(sessionReplaySceneLogic)
const { currentTeam } = useValues(teamLogic)
const recordingsDisabled = currentTeam && !currentTeam?.session_recording_opt_in
@@ -84,17 +79,11 @@ function Header(): JSX.Element {
data-attr="session-recordings-filters-save-as-playlist"
type="primary"
onClick={(e) =>
- guardAvailableFeature(
- AvailableFeature.RECORDINGS_PLAYLISTS,
- () => {
- // choose the type of playlist handler so that analytics correctly report
- // whether filters have been changed before saving
- totalFiltersCount === 0
- ? newPlaylistHandler.onEvent?.(e)
- : saveFiltersPlaylistHandler.onEvent?.(e)
- },
- { currentUsage: playlists.count }
- )
+ // choose the type of playlist handler so that analytics correctly report
+ // whether filters have been changed before saving
+ totalFiltersCount === 0
+ ? newPlaylistHandler.onEvent?.(e)
+ : saveFiltersPlaylistHandler.onEvent?.(e)
}
>
Save as playlist
@@ -112,13 +101,7 @@ function Header(): JSX.Element {
{tab === ReplayTabs.Playlists && (
- guardAvailableFeature(
- AvailableFeature.RECORDINGS_PLAYLISTS,
- () => newPlaylistHandler.onEvent?.(e),
- { currentUsage: playlists.count }
- )
- }
+ onClick={(e) => newPlaylistHandler.onEvent?.(e)}
data-attr="save-recordings-playlist-button"
loading={newPlaylistHandler.loading}
>
diff --git a/frontend/src/scenes/session-recordings/saved-playlists/SavedSessionRecordingPlaylistsEmptyState.tsx b/frontend/src/scenes/session-recordings/saved-playlists/SavedSessionRecordingPlaylistsEmptyState.tsx
index e2933ff1ddda3..beeb6c49f5030 100644
--- a/frontend/src/scenes/session-recordings/saved-playlists/SavedSessionRecordingPlaylistsEmptyState.tsx
+++ b/frontend/src/scenes/session-recordings/saved-playlists/SavedSessionRecordingPlaylistsEmptyState.tsx
@@ -1,18 +1,16 @@
import { IconPlus } from '@posthog/icons'
import { useValues } from 'kea'
-import { upgradeModalLogic } from 'lib/components/UpgradeModal/upgradeModalLogic'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
-import { AvailableFeature, ReplayTabs } from '~/types'
+import { ReplayTabs } from '~/types'
import { createPlaylist } from '../playlist/playlistUtils'
import { savedSessionRecordingPlaylistsLogic } from './savedSessionRecordingPlaylistsLogic'
export function SavedSessionRecordingPlaylistsEmptyState(): JSX.Element {
- const { guardAvailableFeature } = useValues(upgradeModalLogic)
const playlistsLogic = savedSessionRecordingPlaylistsLogic({ tab: ReplayTabs.Home })
- const { playlists, loadPlaylistsFailed } = useValues(playlistsLogic)
+ const { loadPlaylistsFailed } = useValues(playlistsLogic)
return loadPlaylistsFailed ? (
Error while trying to load playlist.
) : (
@@ -24,13 +22,7 @@ export function SavedSessionRecordingPlaylistsEmptyState(): JSX.Element {
type="primary"
data-attr="add-session-playlist-button-empty-state"
icon={}
- onClick={() =>
- guardAvailableFeature(
- AvailableFeature.RECORDINGS_PLAYLISTS,
- () => void createPlaylist({}, true),
- { currentUsage: playlists.count }
- )
- }
+ onClick={() => void createPlaylist({}, true)}
>
New playlist
diff --git a/posthog/session_recordings/queries/session_replay_events.py b/posthog/session_recordings/queries/session_replay_events.py
index 87c90845fcb56..a97e92e76b369 100644
--- a/posthog/session_recordings/queries/session_replay_events.py
+++ b/posthog/session_recordings/queries/session_replay_events.py
@@ -168,8 +168,8 @@ def get_events(
def ttl_days(team: Team) -> int:
if is_cloud():
- # NOTE: We use Playlists as a proxy to see if they are subbed to Recordings
- is_paid = team.organization.is_feature_available(AvailableFeature.RECORDINGS_PLAYLISTS)
+ # NOTE: We use file export as a proxy to see if they are subbed to Recordings
+ is_paid = team.organization.is_feature_available(AvailableFeature.RECORDINGS_FILE_EXPORT)
ttl_days = settings.REPLAY_RETENTION_DAYS_MAX if is_paid else settings.REPLAY_RETENTION_DAYS_MIN
# NOTE: The date we started reliably ingested data to blob storage
diff --git a/posthog/session_recordings/queries/test/test_session_recording_list_from_query.py b/posthog/session_recordings/queries/test/test_session_recording_list_from_query.py
index 0301f3a12124e..a548a4e93c16f 100644
--- a/posthog/session_recordings/queries/test/test_session_recording_list_from_query.py
+++ b/posthog/session_recordings/queries/test/test_session_recording_list_from_query.py
@@ -891,7 +891,7 @@ def test_ttl_days(self):
assert ttl_days(self.team) == 30
self.team.organization.available_product_features = [
- {"key": AvailableFeature.RECORDINGS_PLAYLISTS, "name": AvailableFeature.RECORDINGS_PLAYLISTS}
+ {"key": AvailableFeature.RECORDINGS_FILE_EXPORT, "name": AvailableFeature.RECORDINGS_FILE_EXPORT}
]
# Far enough in the future from `days_since_blob_ingestion` but paid