Repeating Panels loading Slowly

I have a page that is essentially a gui for an app table, it just feeds row objects into a repeating panel. The problem is that it takes 5 -8 seconds to load every time. It only has about two hundred rows.

here is the front end

from ._anvil_designer import CasesTemplate
from anvil import *
import anvil.server
import anvil.facebook.auth
import anvil.microsoft.auth
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
from ..routing import goHome

class Cases(CasesTemplate):
  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)
    self.image_1.height = self.image_1.height
    # Any code you write here will run before the form opens.
    self.caseContent.items = anvil.server.call('get_active_cases')
    self.searchContext = False
    self.caseContent.ArchState = False
    self.leadParaInput.items = anvil.server.call('getParalegals')
   

  def addCase_click(self, **event_args):
    """This method is called when the button is clicked"""
    self.addCaseCard.visible = True
    self.addCase.icon = "fa:angle-double-up"
    self.addCase.add_event_handler('click',self.addCase_click_up)


  def addCase_click_up(self, **event_args):
    """This method is called when the button is clicked"""
    self.addCaseCard.visible = False
    self.addCase.icon= "fa:plus"
    self.addCase.add_event_handler('click',self.addCase_click)
  

  def submitAdd_click(self, **event_args):
    """This method is called when the button is clicked"""
    caseDetails = {}
    #get title 
    caseDetails.update({"Case":self.caseTitle.text})
    for comp in self.caseDetails.get_components():
      if "input" in str(comp.tag):
        if "select" in str(comp.tag):
          col = str(comp.tag).replace("select|input|","")
          caseDetails[col] = comp.selected_value
        else: 
          col = str(comp.tag).replace("input|","")
          caseDetails[col] = comp.text
    addStat = anvil.server.call('addCase',caseDetails)
    if addStat != "Success":
      alert(addStat)
      return -1
    else:   
      alert("Case Submitted")
      self.addCase_click_up()
    

  def search_click(self, **event_args):
    """This method is called when the button is clicked"""
    self.clearSearch.visible = True
    self.caseContent.items = anvil.server.call('search_cases',query=self.searchVal.text,searchContext = self.searchContext)

  
  def clearSearch_click(self, **event_args):
    self.clearSearch.visible = False
    self.caseContent.items = anvil.server.call('get_cases')
    self.searchVal.text = None

  def home_click(self, **event_args):
    """This method is called when the button is clicked"""
    goHome()

  def active_click(self, **event_args):
    """This method is called when the button is clicked"""
    self.caseContent.items = anvil.server.call('get_active_cases')
    self.SearchContext = False
    self.caseContent.ArchState = False
    self.active.background = "#EADDFF"
    self.archived.background = 'white'
    self.all.background = 'white'

  def archived_click(self, **event_args):
    """This method is called when the button is clicked"""
    self.caseContent.items = anvil.server.call('get_archived_cases')
    self.SearchContext = True
    self.caseContent.ArchState = True
    self.active.background = "white"
    self.archived.background = "#EADDFF"
    self.all.background = 'white'

  def all_click(self, **event_args):
    """This method is called when the button is clicked"""
    self.caseContent.items = anvil.server.call('get_cases')
    self.SearchContext = None
    self.caseContent.ArchState = None
    self.active.background = "white"
    self.archived.background = "white"
    self.all.background = "#EADDFF"

here is the back end

import anvil.files
from anvil.files import data_files
import anvil.email
import anvil.facebook.auth
import anvil.microsoft.auth
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.server

@anvil.server.callable
def get_cases():
  return app_tables.cases.search(tables.order_by("Case"))

@anvil.server.callable
def get_active_cases():
  return app_tables.cases.search(tables.order_by("Case"),Archive=False)

@anvil.server.callable
def get_archived_cases():
  return app_tables.cases.search(tables.order_by("Case"),Archive=True)

@anvil.server.callable
def getParalegals():
  names = [] 
  users = app_tables.users.search()
  atts = ['Associate','Partner']
  for user in users:
    if user['role'] not in atts:
      name = f"{user['full_name']}, ({user['email']})"
      names.append(name)
  return names

@anvil.server.callable
def archiveCase(case):
  if case['Archive'] == True:
    case['Archive'] = False
  else:
    case['Archive'] = True
    

@anvil.server.callable
def search_cases(query,searchContext):
  if searchContext == None:
    result = app_tables.cases.search()
    results = []
    headers = (app_tables.cases.list_columns())
    for row in result:
      for header in headers:
        header_name = (header['name'])
        if str(query).lower().strip() in str(row[header_name]).lower().strip():
          results.append(row)
          break
  else:
    result = app_tables.cases.search(Archive=searchContext)
    results = []
    headers = (app_tables.cases.list_columns())
    for row in result:
      for header in headers:
        header_name = (header['name'])
        if str(query).lower().strip() in str(row[header_name]).lower().strip():
          results.append(row)
          break
    
  return results


@anvil.server.callable
def addCase(data):
  if app_tables.cases.get(Case=data['Case']) != None:
    return "Case Already Exists"
  #add lead 
  if data['Lead_Paralegal'] != None:
    leadParaEmail = data['Lead_Paralegal'].split(",")[1].replace("(","").replace(")"," ").replace(" ","")
    para = app_tables.users.get(email=leadParaEmail)
    data['Lead_Paralegal'] = para
  app_tables.cases.add_row(**data)
  return "Success"

@anvil.server.callable
def add_case(case,partner,associate,coCounsel,callsGoTo,leadParalegal):
  app_tables.cases.add_row(Case=case,Partner=partner,Associate=associate,Calls_Go_To=callsGoTo,Lead_Paralegal=leadParalegal)

@anvil.server.callable
def edit_Case(data,row):
  if data['Lead_Paralegal'] != None:
    leadParaEmail = data['Lead_Paralegal'].split(",")[1].replace("(","").replace(")"," ").replace(" ","")
    para = app_tables.users.get(email=leadParaEmail)
    data['Lead_Paralegal'] = para
  row.update(**data)


@anvil.server.callable
def addStaff(staffmember,case):
  staffemail = staffmember.split(",")[1].replace("(","").replace(")"," ").replace(" ","")
  staffmember_obj = app_tables.users.get(email=staffemail)
  if case['Staff'] == None:
    case['Staff'] = [staffmember_obj]
  else:
    case['Staff'] += [staffmember_obj]
  
@anvil.server.callable
def removeStaff(staffmember,case):
  case['Staff'] = [r for r in case['Staff'] if r != staffmember]  
  
  
@anvil.server.callable
def linkUsertoCase():
  logs = app_tables.case_level_work_logs.search()
  for log in  logs:
    case = log['Case']
    user = log['Employee']
    if case['Worked On'] == None or user not in case['Worked On']:
        if case['Worked On'] == None:
          case['Worked On'] = [user]
        else:
          case['Worked On'] += [user]
    

As others will no doubt suggest soon, the key to finding a bottleneck is measurement. Human intuition is notoriously bad at finding these things, especially in complex, shared environments.

You’ll find many examples in the Forum on how to measure elapsed time, both client-side and server-side, between two points in code. You can use this to find your bottlenecks.

Do not expect client and server clocks to be in perfect sync.

Rule number one to speed up a form is to make sure you only have one round trip when the form appears and one round trip per user action.

Unfortunately Anvil doesn’t tell you when round trips happen, I would love to see them all, but for now you will need to use your imagination.

Your form starts with two server calls:

  def __init__(self, **properties):
    [...]
    self.caseContent.items = anvil.server.call('get_active_cases')
    [...]
    self.leadParaInput.items = anvil.server.call('getParalegals')

You can make it faster by creating one server function that returns a dictionary with two lists of items, then make one server call only:

  def __init__(self, **properties):
    [...]
    all_items = anvil.server.call('get_active_cases_and_paralegals')
    self.caseContent.items = all_items['active_cases']
    [...]
    self.leadParaInput.items = all_items['paralegals']

You may also have unintentional round trips triggered by the row objects. Some column types are loaded lazily, that is, rather than being sent to the client when the server function is called, they are fetched the first time they are accessed. So when the forms in the repeating panel are rendered, they may trigger a bunch of round trips.

You can manage this by enabling accelerated tables and using fetch_only to fine tune what’s sent to the client immediately because you know you will need it, and what shouldn’t be sent because you know you will not need it.

1 Like

It would also help to see what happens inside your row template. People sometimes have server calls hidden inside there (or other calls that involve round trips, such as anvil.users.get_user).

2 Likes

Yes, and I would like to see them all highlighted in the app console, as mentioned in the FR.

It’s difficult to improve something that you don’t even know it exists.

from ._anvil_designer import ItemTemplate5Template
from anvil import *
import anvil.server
import anvil.facebook.auth
import anvil.microsoft.auth
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
from …routing import goCasePage

class ItemTemplate5(ItemTemplate5Template):
def init(self, **properties):
# Set Form properties and Data Bindings.
self.init_components(**properties)
# Any code you write here will run before the form opens.
if self.item[‘Lead_Paralegal’] != None:
para = self.item[‘Lead_Paralegal’]
self.leadPara.text = para[‘full_name’]
self.leadParaInput.selected_value = f"{para[‘full_name’]}, ({para[‘email’]})"
if self.item[‘Archive’] == True:
self.archive.text = “Activate Case”
self.archive.icon = “fa:bolt”

def edit_click(self, **event_args):
“”“This method is called when the button is clicked”“”
self.submitEdit.visible = True
#make input boxes appear
for comp in self.caseDetails.get_components():
if “input” in str(comp.tag):
comp.visible = True
elif “display” == str(comp.tag):
comp.visible = False
self.leadParaInput.items = anvil.server.call(‘getParalegals’)
self.edit.icon = “fa:angle-double-up”
self.edit.add_event_handler(‘click’,self.edit_click_up)

def edit_click_up(self, **event_args):
self.submitEdit.visible = False
#make input boxes appear
for comp in self.caseDetails.get_components():
if “input” in str(comp.tag):
comp.visible = False
elif “display” == str(comp.tag):
comp.visible = True
self.edit.icon= “fa:edit”
self.edit.add_event_handler(‘click’,self.edit_click)

def submitEdit_click(self, **event_args):
“”“This method is called when the button is clicked”“”
caseDetails = {}
for comp in self.caseDetails.get_components():
if “input” in str(comp.tag):
if “select” in str(comp.tag):
col = str(comp.tag).replace(“select|input|”,“”)
caseDetails[col] = comp.selected_value
else:
col = str(comp.tag).replace(“input|”,“”)
caseDetails[col] = comp.text
row = self.item
anvil.server.call(‘edit_Case’,data=caseDetails,row=row)
alert(“Case Submitted”)
self.edit_click_up()

def toCasePage_click(self, **event_args):
“”“This method is called when the button is clicked”“”
goCasePage(self.item)

def archive_click(self, **event_args):
“”“This method is called when the button is clicked”“”
case = self.item
anvil.server.call(“archiveCase”,case=case)
self.remove_from_parent()

The forum does not reproduce Python’s necessary indentation unless you tell it to do that.

Put
```
in a line by itself before your code, and after. Then folks will be able to read your code as you wrote it.

1 Like