adjust registered by to managed by on db aslo remove url and api key on adding sites for gm
This commit is contained in:
1 parent
64b37b6b6d
commit
521f36d22d
15 files changed
+112
-75
No files matched your search
+2
-2
@@ -17,8 +17,8 @@ Main-office **mirror API** for the executive frontend. Domain shapes match site
|
|||||||
`bootstrap_admin` / seed `admin` uses status `superuser`. This app does not use `is_staff`.
|
`bootstrap_admin` / seed `admin` uses status `superuser`. This app does not use `is_staff`.
|
||||||
|
|
||||||
GM↔BUH link: `User.managed_by` (confirmed). **Only `gm`** may request a **`buh`** (not `bu_admin` / `gm_admin`); **only that `buh`** may approve.
|
GM↔BUH link: `User.managed_by` (confirmed). **Only `gm`** may request a **`buh`** (not `bu_admin` / `gm_admin`); **only that `buh`** may approve.
|
||||||
Active site registry ownership: `ActiveSite.registered_by`.
|
Active site registry ownership: `ActiveSite.managed_by`.
|
||||||
GM farm visibility follows the Pengaturan active-site list: only HQ `Site` rows linked to an enabled registered `ActiveSite` are shown (empty list ⇒ no sites).
|
GM farm visibility follows the Pengaturan active-site list: only HQ `Site` rows linked to an enabled `ActiveSite` they manage are shown (empty list ⇒ no sites).
|
||||||
BUH farm visibility is the union of those lists for **confirmed** managed GMs (read-only Pengaturan rollup; optional GM filter in the header).
|
BUH farm visibility is the union of those lists for **confirmed** managed GMs (read-only Pengaturan rollup; optional GM filter in the header).
|
||||||
|
|
||||||
`POST /api/v1/cycles/{id}/approve-close/` — GM only.
|
`POST /api/v1/cycles/{id}/approve-close/` — GM only.
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
- director / director_admin: all sites / active sites / GMs
|
- director / director_admin: all sites / active sites / GMs
|
||||||
- buh / bu_admin: only GMs (and gm_admins) they manage, plus those GMs' sites
|
- buh / bu_admin: only GMs (and gm_admins) they manage, plus those GMs' sites
|
||||||
- gm / gm_admin: only their own sites / active sites they registered
|
- gm / gm_admin: only their own sites / active sites they manage
|
||||||
|
|
||||||
Farm Site rows are only visible to non-directors when linked to an active
|
Farm Site rows are only visible to non-directors when linked to an enabled
|
||||||
ActiveSite in the viewer's Pengaturan registry.
|
ActiveSite in the viewer's Pengaturan registry.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ def managed_gm_ids(buh: User) -> list[int]:
|
|||||||
|
|
||||||
def visible_owner_ids(user: User) -> list[int] | None:
|
def visible_owner_ids(user: User) -> list[int] | None:
|
||||||
"""
|
"""
|
||||||
Site.user / ActiveSite.registered_by ids this viewer may see.
|
Site.user / ActiveSite.managed_by ids this viewer may see.
|
||||||
|
|
||||||
Returns None when there is no restriction (director / superuser).
|
Returns None when there is no restriction (director / superuser).
|
||||||
"""
|
"""
|
||||||
@@ -89,7 +89,7 @@ def visible_active_site_ids(user: User) -> list[int] | None:
|
|||||||
return []
|
return []
|
||||||
return list(
|
return list(
|
||||||
ActiveSite.objects.filter(
|
ActiveSite.objects.filter(
|
||||||
registered_by_id__in=owners,
|
managed_by_id__in=owners,
|
||||||
is_active=True,
|
is_active=True,
|
||||||
).values_list("pk", flat=True)
|
).values_list("pk", flat=True)
|
||||||
)
|
)
|
||||||
@@ -132,7 +132,7 @@ def filter_active_sites_queryset(qs: QuerySet, viewer: User) -> QuerySet:
|
|||||||
return qs
|
return qs
|
||||||
if not owners:
|
if not owners:
|
||||||
return qs.none()
|
return qs.none()
|
||||||
return qs.filter(registered_by_id__in=owners)
|
return qs.filter(managed_by_id__in=owners)
|
||||||
|
|
||||||
|
|
||||||
def filter_kandangs_queryset(qs: QuerySet, viewer: User) -> QuerySet:
|
def filter_kandangs_queryset(qs: QuerySet, viewer: User) -> QuerySet:
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ class VisibilityScopeTests(TestCase):
|
|||||||
code="a",
|
code="a",
|
||||||
name="Site A",
|
name="Site A",
|
||||||
api_base_url="https://a.example/api/v1",
|
api_base_url="https://a.example/api/v1",
|
||||||
registered_by=self.gm,
|
managed_by=self.gm,
|
||||||
)
|
)
|
||||||
self.active_b = ActiveSite.objects.create(
|
self.active_b = ActiveSite.objects.create(
|
||||||
code="b",
|
code="b",
|
||||||
name="Site B",
|
name="Site B",
|
||||||
api_base_url="https://b.example/api/v1",
|
api_base_url="https://b.example/api/v1",
|
||||||
registered_by=self.gm_other,
|
managed_by=self.gm_other,
|
||||||
)
|
)
|
||||||
self.site = Site.objects.create(
|
self.site = Site.objects.create(
|
||||||
site_name="Site A", user=self.gm, active_site=self.active_a
|
site_name="Site A", user=self.gm, active_site=self.active_a
|
||||||
@@ -98,7 +98,6 @@ class VisibilityScopeTests(TestCase):
|
|||||||
{
|
{
|
||||||
"code": "orphan-seed",
|
"code": "orphan-seed",
|
||||||
"name": "Orphan Seed",
|
"name": "Orphan Seed",
|
||||||
"api_base_url": "https://orphan.example/api/v1",
|
|
||||||
"is_active": True,
|
"is_active": True,
|
||||||
},
|
},
|
||||||
format="json",
|
format="json",
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ def ensure_hq_site_for_active_site(active_site: ActiveSite) -> Site:
|
|||||||
if existing:
|
if existing:
|
||||||
return existing
|
return existing
|
||||||
|
|
||||||
owner = active_site.registered_by
|
owner = active_site.managed_by
|
||||||
if owner is None:
|
if owner is None:
|
||||||
raise ValueError("ActiveSite.registered_by is required to create an HQ site.")
|
raise ValueError("ActiveSite.managed_by is required to create an HQ site.")
|
||||||
|
|
||||||
site_name = (active_site.name or active_site.code or "Site")[:30]
|
site_name = (active_site.name or active_site.code or "Site")[:30]
|
||||||
orphan = (
|
orphan = (
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ from apps.sync.models import ActiveSite
|
|||||||
|
|
||||||
|
|
||||||
def _gm_visible_site(*, owner: User, site_name: str, code: str) -> Site:
|
def _gm_visible_site(*, owner: User, site_name: str, code: str) -> Site:
|
||||||
"""GM scope only sees sites linked to an ActiveSite they registered."""
|
"""GM scope only sees sites linked to an ActiveSite they manage."""
|
||||||
active_site = ActiveSite.objects.create(
|
active_site = ActiveSite.objects.create(
|
||||||
code=code,
|
code=code,
|
||||||
name=site_name,
|
name=site_name,
|
||||||
api_base_url=f"https://{code}.example/api/v1",
|
api_base_url=f"https://{code}.example/api/v1",
|
||||||
registered_by=owner,
|
managed_by=owner,
|
||||||
)
|
)
|
||||||
return Site.objects.create(
|
return Site.objects.create(
|
||||||
site_name=site_name, user=owner, active_site=active_site
|
site_name=site_name, user=owner, active_site=active_site
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ def _upsert_mirror_cycle_from_ingest(active_site: ActiveSite, payload: dict) ->
|
|||||||
|
|
||||||
site_name = str(payload.get("site_name") or active_site.name)[:30]
|
site_name = str(payload.get("site_name") or active_site.name)[:30]
|
||||||
kandang_name = str(payload.get("kandang_name") or "Kandang")[:30]
|
kandang_name = str(payload.get("kandang_name") or "Kandang")[:30]
|
||||||
owner = active_site.registered_by
|
owner = active_site.managed_by
|
||||||
if owner is None:
|
if owner is None:
|
||||||
owner = User.objects.filter(status=User.STATUS_GM).first()
|
owner = User.objects.filter(status=User.STATUS_GM).first()
|
||||||
if owner is None:
|
if owner is None:
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ class Command(BaseCommand):
|
|||||||
if not staff:
|
if not staff:
|
||||||
return
|
return
|
||||||
moved_sites = Site.objects.filter(user=staff).update(user=gm)
|
moved_sites = Site.objects.filter(user=staff).update(user=gm)
|
||||||
moved_active = ActiveSite.objects.filter(registered_by=staff).update(
|
moved_active = ActiveSite.objects.filter(managed_by=staff).update(
|
||||||
registered_by=gm
|
managed_by=gm
|
||||||
)
|
)
|
||||||
ApiKey.objects.filter(user=staff).delete()
|
ApiKey.objects.filter(user=staff).delete()
|
||||||
staff.delete()
|
staff.delete()
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("sync", "0003_rename_citysite_to_activesite"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="activesite",
|
||||||
|
name="api_base_url",
|
||||||
|
field=models.URLField(
|
||||||
|
blank=True,
|
||||||
|
default="",
|
||||||
|
help_text="Optional site API root including /api/v1 (for outbound sync/push)",
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="activesite",
|
||||||
|
name="registered_by",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
help_text="GM / GM Admin who registered this site by name and code",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="registered_active_sites",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated manually: rename ActiveSite.registered_by → managed_by
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("sync", "0004_activesite_api_base_url_optional"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="activesite",
|
||||||
|
old_name="registered_by",
|
||||||
|
new_name="managed_by",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="activesite",
|
||||||
|
name="managed_by",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
help_text="GM / GM Admin who manages this active site",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="managed_active_sites",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -17,20 +17,22 @@ class ActiveSite(models.Model):
|
|||||||
name = models.CharField(max_length=120)
|
name = models.CharField(max_length=120)
|
||||||
api_base_url = models.URLField(
|
api_base_url = models.URLField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text="Site API root including /api/v1, e.g. https://site.example/api/v1",
|
blank=True,
|
||||||
|
default="",
|
||||||
|
help_text="Optional site API root including /api/v1 (for outbound sync/push)",
|
||||||
)
|
)
|
||||||
api_key = models.CharField(
|
api_key = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="X-API-Key for pulling from the site API (keep server-side only)",
|
help_text="X-API-Key for pulling from the site API (keep server-side only)",
|
||||||
)
|
)
|
||||||
registered_by = models.ForeignKey(
|
managed_by = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
related_name="registered_active_sites",
|
related_name="managed_active_sites",
|
||||||
help_text="GM / GM Admin who added this site dashboard URL",
|
help_text="GM / GM Admin who manages this active site",
|
||||||
)
|
)
|
||||||
is_active = models.BooleanField(default=True)
|
is_active = models.BooleanField(default=True)
|
||||||
last_synced_at = models.DateTimeField(null=True, blank=True)
|
last_synced_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ class ActiveSiteSerializer(serializers.ModelSerializer):
|
|||||||
id = serializers.IntegerField(source="pk", read_only=True)
|
id = serializers.IntegerField(source="pk", read_only=True)
|
||||||
# Never echo the outbound site API key back to the browser by default.
|
# Never echo the outbound site API key back to the browser by default.
|
||||||
has_api_key = serializers.SerializerMethodField()
|
has_api_key = serializers.SerializerMethodField()
|
||||||
registered_by_name = serializers.CharField(
|
managed_by_name = serializers.CharField(
|
||||||
source="registered_by.display_name", read_only=True, allow_null=True
|
source="managed_by.display_name", read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -19,8 +19,8 @@ class ActiveSiteSerializer(serializers.ModelSerializer):
|
|||||||
"name",
|
"name",
|
||||||
"api_base_url",
|
"api_base_url",
|
||||||
"has_api_key",
|
"has_api_key",
|
||||||
"registered_by",
|
"managed_by",
|
||||||
"registered_by_name",
|
"managed_by_name",
|
||||||
"is_active",
|
"is_active",
|
||||||
"last_synced_at",
|
"last_synced_at",
|
||||||
"last_sync_status",
|
"last_sync_status",
|
||||||
@@ -31,8 +31,8 @@ class ActiveSiteSerializer(serializers.ModelSerializer):
|
|||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"id",
|
"id",
|
||||||
"has_api_key",
|
"has_api_key",
|
||||||
"registered_by",
|
"managed_by",
|
||||||
"registered_by_name",
|
"managed_by_name",
|
||||||
"last_synced_at",
|
"last_synced_at",
|
||||||
"last_sync_status",
|
"last_sync_status",
|
||||||
"last_sync_message",
|
"last_sync_message",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class ActiveSiteViewSet(viewsets.ModelViewSet):
|
|||||||
executive_sync_action = True
|
executive_sync_action = True
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = ActiveSite.objects.select_related("registered_by").all()
|
qs = ActiveSite.objects.select_related("managed_by").all()
|
||||||
return filter_active_sites_queryset(qs, self.request.user)
|
return filter_active_sites_queryset(qs, self.request.user)
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
@@ -36,7 +36,7 @@ class ActiveSiteViewSet(viewsets.ModelViewSet):
|
|||||||
return ActiveSiteSerializer
|
return ActiveSiteSerializer
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
active_site = serializer.save(registered_by=self.request.user)
|
active_site = serializer.save(managed_by=self.request.user)
|
||||||
ensure_hq_site_for_active_site(active_site)
|
ensure_hq_site_for_active_site(active_site)
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ const BuhViewList: React.FC = () => {
|
|||||||
const sitesByGm = useMemo(() => {
|
const sitesByGm = useMemo(() => {
|
||||||
const map = new Map<number, ActiveSite[]>();
|
const map = new Map<number, ActiveSite[]>();
|
||||||
for (const site of activeSites) {
|
for (const site of activeSites) {
|
||||||
if (site.registered_by == null) continue;
|
if (site.managed_by == null) continue;
|
||||||
const rows = map.get(site.registered_by) ?? [];
|
const rows = map.get(site.managed_by) ?? [];
|
||||||
rows.push(site);
|
rows.push(site);
|
||||||
map.set(site.registered_by, rows);
|
map.set(site.managed_by, rows);
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}, [activeSites]);
|
}, [activeSites]);
|
||||||
@@ -224,8 +224,6 @@ const GmViewList: React.FC = () => {
|
|||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
const [code, setCode] = useState('');
|
const [code, setCode] = useState('');
|
||||||
const [codeTouched, setCodeTouched] = useState(false);
|
const [codeTouched, setCodeTouched] = useState(false);
|
||||||
const [apiBaseUrl, setApiBaseUrl] = useState('');
|
|
||||||
const [apiKey, setApiKey] = useState('');
|
|
||||||
|
|
||||||
const flash = (msg: string) => {
|
const flash = (msg: string) => {
|
||||||
setNotice(msg);
|
setNotice(msg);
|
||||||
@@ -263,8 +261,6 @@ const GmViewList: React.FC = () => {
|
|||||||
setName('');
|
setName('');
|
||||||
setCode('');
|
setCode('');
|
||||||
setCodeTouched(false);
|
setCodeTouched(false);
|
||||||
setApiBaseUrl('');
|
|
||||||
setApiKey('');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrap = async (fn: () => Promise<void>) => {
|
const wrap = async (fn: () => Promise<void>) => {
|
||||||
@@ -314,7 +310,7 @@ const GmViewList: React.FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Pengaturan"
|
title="Pengaturan"
|
||||||
subtitle="Pilih BUH Anda, lalu daftarkan site ke daftar tampilan"
|
subtitle="Pilih BUH Anda, lalu daftarkan site dengan nama dan kode"
|
||||||
/>
|
/>
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
@@ -400,7 +396,8 @@ const GmViewList: React.FC = () => {
|
|||||||
<section className="bg-white rounded-xl shadow-sm p-5">
|
<section className="bg-white rounded-xl shadow-sm p-5">
|
||||||
<h2 className="font-semibold text-gray-800 mb-1">Tambah site</h2>
|
<h2 className="font-semibold text-gray-800 mb-1">Tambah site</h2>
|
||||||
<p className="text-xs text-gray-500 mb-4">
|
<p className="text-xs text-gray-500 mb-4">
|
||||||
Daftarkan URL API dasbor kota agar data site muncul di daftar tampilan Anda.
|
Masukkan nama dan kode yang sama dengan yang diisi manajer di dasbor site, agar site
|
||||||
|
terhubung ke daftar tampilan Anda.
|
||||||
</p>
|
</p>
|
||||||
<form
|
<form
|
||||||
className="space-y-3"
|
className="space-y-3"
|
||||||
@@ -412,8 +409,6 @@ const GmViewList: React.FC = () => {
|
|||||||
const created = await api.activeSites.create({
|
const created = await api.activeSites.create({
|
||||||
code: resolvedCode,
|
code: resolvedCode,
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
api_base_url: apiBaseUrl.trim(),
|
|
||||||
...(apiKey.trim() ? { api_key: apiKey.trim() } : {}),
|
|
||||||
is_active: true,
|
is_active: true,
|
||||||
});
|
});
|
||||||
resetForm();
|
resetForm();
|
||||||
@@ -448,28 +443,6 @@ const GmViewList: React.FC = () => {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label className="block text-sm text-gray-700">
|
|
||||||
URL API kota
|
|
||||||
<input
|
|
||||||
className={`${inputClass} mt-1`}
|
|
||||||
type="url"
|
|
||||||
placeholder="https://site.example/api/v1"
|
|
||||||
value={apiBaseUrl}
|
|
||||||
onChange={(e) => setApiBaseUrl(e.target.value)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label className="block text-sm text-gray-700">
|
|
||||||
API key (opsional)
|
|
||||||
<input
|
|
||||||
className={`${inputClass} mt-1`}
|
|
||||||
type="password"
|
|
||||||
autoComplete="off"
|
|
||||||
placeholder="X-API-Key untuk sinkron dari kota"
|
|
||||||
value={apiKey}
|
|
||||||
onChange={(e) => setApiKey(e.target.value)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-semibold disabled:opacity-50"
|
className="bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-semibold disabled:opacity-50"
|
||||||
@@ -496,7 +469,6 @@ const GmViewList: React.FC = () => {
|
|||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-sm font-semibold text-gray-800 truncate">{site.name}</p>
|
<p className="text-sm font-semibold text-gray-800 truncate">{site.name}</p>
|
||||||
<p className="text-xs text-gray-500 truncate">{site.code}</p>
|
<p className="text-xs text-gray-500 truncate">{site.code}</p>
|
||||||
<p className="text-xs text-gray-400 truncate mt-0.5">{site.api_base_url}</p>
|
|
||||||
<p className="text-xs mt-1">
|
<p className="text-xs mt-1">
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
@@ -507,7 +479,6 @@ const GmViewList: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{site.is_active ? 'Aktif' : 'Nonaktif'}
|
{site.is_active ? 'Aktif' : 'Nonaktif'}
|
||||||
</span>
|
</span>
|
||||||
{site.has_api_key ? ' · API key tersimpan' : ''}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ describe('SettingsPage city site registry', () => {
|
|||||||
name: 'Sukawarna',
|
name: 'Sukawarna',
|
||||||
api_base_url: 'https://sukawarna.example/api/v1',
|
api_base_url: 'https://sukawarna.example/api/v1',
|
||||||
has_api_key: true,
|
has_api_key: true,
|
||||||
registered_by: 5,
|
managed_by: 5,
|
||||||
registered_by_name: 'gm.user',
|
managed_by_name: 'gm.user',
|
||||||
is_active: true,
|
is_active: true,
|
||||||
last_synced_at: null,
|
last_synced_at: null,
|
||||||
last_sync_status: '',
|
last_sync_status: '',
|
||||||
@@ -119,8 +119,8 @@ describe('SettingsPage city site registry', () => {
|
|||||||
name: 'Cikupa',
|
name: 'Cikupa',
|
||||||
api_base_url: 'https://cikupa.example/api/v1',
|
api_base_url: 'https://cikupa.example/api/v1',
|
||||||
has_api_key: false,
|
has_api_key: false,
|
||||||
registered_by: 5,
|
managed_by: 5,
|
||||||
registered_by_name: 'gm.user',
|
managed_by_name: 'gm.user',
|
||||||
is_active: true,
|
is_active: true,
|
||||||
last_synced_at: null,
|
last_synced_at: null,
|
||||||
last_sync_status: '',
|
last_sync_status: '',
|
||||||
@@ -159,16 +159,12 @@ describe('SettingsPage city site registry', () => {
|
|||||||
fireEvent.change(screen.getByPlaceholderText('Contoh: Sukawarna'), {
|
fireEvent.change(screen.getByPlaceholderText('Contoh: Sukawarna'), {
|
||||||
target: { value: 'Cikupa' },
|
target: { value: 'Cikupa' },
|
||||||
});
|
});
|
||||||
fireEvent.change(screen.getByPlaceholderText('https://site.example/api/v1'), {
|
|
||||||
target: { value: 'https://cikupa.example/api/v1' },
|
|
||||||
});
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'Tambah ke daftar' }));
|
fireEvent.click(screen.getByRole('button', { name: 'Tambah ke daftar' }));
|
||||||
|
|
||||||
await waitFor(() => expect(createMock).toHaveBeenCalledTimes(1));
|
await waitFor(() => expect(createMock).toHaveBeenCalledTimes(1));
|
||||||
expect(createMock).toHaveBeenCalledWith({
|
expect(createMock).toHaveBeenCalledWith({
|
||||||
code: 'cikupa',
|
code: 'cikupa',
|
||||||
name: 'Cikupa',
|
name: 'Cikupa',
|
||||||
api_base_url: 'https://cikupa.example/api/v1',
|
|
||||||
is_active: true,
|
is_active: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+3
-3
@@ -45,8 +45,8 @@ export type ActiveSite = {
|
|||||||
name: string;
|
name: string;
|
||||||
api_base_url: string;
|
api_base_url: string;
|
||||||
has_api_key: boolean;
|
has_api_key: boolean;
|
||||||
registered_by?: number | null;
|
managed_by?: number | null;
|
||||||
registered_by_name?: string | null;
|
managed_by_name?: string | null;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
last_synced_at: string | null;
|
last_synced_at: string | null;
|
||||||
last_sync_status: string;
|
last_sync_status: string;
|
||||||
@@ -58,7 +58,7 @@ export type ActiveSite = {
|
|||||||
export type ActiveSiteWrite = {
|
export type ActiveSiteWrite = {
|
||||||
code: string;
|
code: string;
|
||||||
name: string;
|
name: string;
|
||||||
api_base_url: string;
|
api_base_url?: string;
|
||||||
api_key?: string;
|
api_key?: string;
|
||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user