nav updates

This commit is contained in:
Peter Howell 2025-09-26 22:11:11 +00:00
parent 3a6bed425b
commit b00f2c329c
1 changed files with 98 additions and 85 deletions

View File

@ -2424,11 +2424,9 @@ def remove_n_analytics(section=0):
import csv
def my_nav_filter(row):
# Return True for tabs that should be considered ON for reporting.
# A nav item is ON if visibility is 'public' and 'hidden' is n/a or False.
# Filter logic: consider a tab ON only when visibility is public and not hidden; used for CSV summary.
if str(row.get('visibility', '')).lower() != 'public':
return False
# Treat missing/"n/a" as not hidden
hidden = row.get('hidden')
if str(hidden).lower() in ['true']:
return False
@ -2437,7 +2435,7 @@ def my_nav_filter(row):
## Multi-pass course nav tool. Pass 1: index + XLSX; Pass 2: optional hide/show ids.
def clean_course_nav_setup_semester(section=0):
# Multi-pass course nav tool: Pass 1 indexes tabs + writes XLSX matrix; Pass 2 optionally hides/shows selected tab ids across all courses in the term.
# Multi-pass tool: index course nav, write XLSX with x/-/blank, then optionally bulk hide/show by label across the term.
TESTING = False # Limit to first 10 courses while testing
import openpyxl
from openpyxl.utils import get_column_letter
@ -2454,13 +2452,13 @@ def clean_course_nav_setup_semester(section=0):
print("Fetching list of all active courses")
courses_in_term = getCoursesInTerm(term, 1, 0)
if TESTING:
print("TESTING mode enabled: limiting to first 10 courses")
courses_in_term = courses_in_term[:10]
print("TESTING mode enabled: limiting to first 20 courses")
courses_in_term = courses_in_term[:20]
# Collect course records and their tabs
all_tabs_by_course = {} # course_id -> list of tab dicts
all_tab_ids = {} # tab_id -> label (most recent seen) — kept for Pass 2 id operations
any_on_labels = set() # labels that are ON in at least one course
all_labels = set() # all labels seen in any course (visible or hidden)
# Also write a detailed CSV of visible tabs as before
nav_out = codecs.open(f'cache/course_nav_summary_{SEM}.csv', 'w', 'utf-8')
@ -2480,6 +2478,8 @@ def clean_course_nav_setup_semester(section=0):
T['hidden'] = "n/a"
# Track global set of tab ids and a sample label
all_tab_ids[str(T.get('id'))] = T.get('label', str(T.get('id')))
if T.get('label'):
all_labels.add(T.get('label'))
# Write summary of ON tabs
vals = [C['id'], C['name'], C['course_code'], C.get('start_at', ''), C.get('workflow_state', ''),
T.get('label', ''), T.get('position', ''), T.get('hidden', ''), T.get('visibility', ''),
@ -2487,17 +2487,53 @@ def clean_course_nav_setup_semester(section=0):
mydict = dict(zip(columns, vals))
if my_nav_filter(mydict):
nav_writer.writerow(vals)
if T.get('label'):
any_on_labels.add(T.get('label'))
nav_out.flush()
all_tabs_by_course[cid] = tabs
except Exception as err:
print(f"Exception: {err}")
try:
nav_out.close()
except Exception:
pass
# Build XLSX matrix
try:
# Column order: labels with at least one ON occurrence, alphabetical
tab_labels = sorted(any_on_labels, key=lambda x: str(x).lower())
# Compute popularity (count of visible 'x' occurrences per label) and sort by popularity then alpha.
# First, build quick lookup per course for label visibility/hidden.
def tab_status(t):
vis = str(t.get('visibility', '')).lower()
hid = str(t.get('hidden', '')).lower()
if vis == 'public' and hid not in ['true']:
return 'x' # present and visible
return '-' # present but hidden (includes non-public visibility)
# Precompute per-course map: label -> status symbol ('x' or '-')
status_by_course = {}
for C in courses_in_term:
cid = str(C['id'])
label_to_status = {}
for tdict in all_tabs_by_course.get(cid, []) or []:
lbl = tdict.get('label')
if not lbl:
continue
cur = label_to_status.get(lbl)
new = tab_status(tdict)
# If any instance is visible, prefer 'x' over '-'
if cur == 'x':
continue
label_to_status[lbl] = 'x' if new == 'x' else (cur or '-')
status_by_course[cid] = label_to_status
# Popularity counts
visible_counts = defaultdict(int)
for cid, lmap in status_by_course.items():
for lbl, sym in lmap.items():
if sym == 'x':
visible_counts[lbl] += 1
# Column order: by decreasing x-count, then alphabetical for ties
tab_labels = sorted(all_labels, key=lambda s: (-visible_counts.get(s, 0), str(s).lower()))
xlsx_path = f"cache/course_nav_matrix_{SEM}.xlsx"
wb = openpyxl.Workbook()
ws = wb.active
@ -2505,8 +2541,8 @@ def clean_course_nav_setup_semester(section=0):
# Headers
static_headers = ['course_id', 'course_name', 'first_teacher']
# Row 1: labels only (ids omitted per request)
row1 = static_headers + tab_labels
# Row 1: labels only, sorted by popularity (most x's first)
row1 = static_headers + list(tab_labels)
ws.append(row1)
# Rows: one per course
@ -2524,18 +2560,10 @@ def clean_course_nav_setup_semester(section=0):
pass
row = [cid, cname, teacher]
# Build a set of labels that are ON for this course
on_labels_in_course = set()
for tdict in all_tabs_by_course.get(cid, []) or []:
visibility = str(tdict.get('visibility', '')).lower()
hidden_val = tdict.get('hidden', '')
hidden_str = str(hidden_val).lower()
if visibility == 'public' and hidden_str not in ['true']:
lbl = tdict.get('label')
if lbl:
on_labels_in_course.add(lbl)
course_map = status_by_course.get(cid, {})
for lbl in tab_labels:
row.append('x' if lbl in on_labels_in_course else '')
sym = course_map.get(lbl, '')
row.append(sym)
ws.append(row)
# Simple sizing for readability
@ -2548,88 +2576,83 @@ def clean_course_nav_setup_semester(section=0):
except Exception as ex:
print(f"Failed to write XLSX matrix: {ex}")
# Optional Pass 2: apply hide/show updates (by id or label)
try_apply = input("Apply changes? [n] hide/show selected tab ids or labels across all courses (y/N): ").strip().lower() in ['y', 'yes']
# Optional Pass 2: apply hide/show updates (labels only)
try_apply = input("Apply changes? [n] hide/show selected labels across all courses (y/N): ").strip().lower() in ['y', 'yes']
if not try_apply:
print("Pass 1 complete. No changes applied.")
return
# Gather ids to HIDE and SHOW
def parse_id_list(s):
# Gather labels to HIDE and SHOW
def parse_label_list(s):
if not s:
return []
# allow comma/space/newline separated
tokens = []
for line in s.replace(',', ' ').split():
tokens.append(line.strip())
return [x for x in tokens if x]
# Allow comma-separated list. Spaces are preserved within labels.
if '\n' in s:
raw = [x.strip() for x in s.split('\n') if x.strip()]
else:
raw = [x.strip() for x in s.split(',') if x.strip()]
return raw
hide_src = input("Enter tab ids OR labels to HIDE (comma/space separated), or path to file in cache/ (leave blank to skip): ").strip()
show_src = input("Enter tab ids OR labels to SHOW (add/unhide), or path to file in cache/ (leave blank to skip): ").strip()
hide_src = input("Enter labels to HIDE (comma-separated or newline file path), leave blank to skip: ").strip()
show_src = input("Enter labels to SHOW (comma-separated or newline file path), leave blank to skip: ").strip()
hide_ids = []
show_ids = []
hide_labels = []
show_labels = []
# file helper
def read_ids_from_path(pth):
def read_labels_from_path(pth):
try:
with codecs.open(pth, 'r', 'utf-8') as f:
return parse_id_list(f.read())
content = f.read()
# Prefer newline separation in files (one label per line)
return [x.strip() for x in content.splitlines() if x.strip()]
except Exception:
return []
# Build a set of all labels observed (from all tabs, not only ON)
all_labels_seen = set()
for _cid, tlist in all_tabs_by_course.items():
for t in (tlist or []):
if t.get('label'):
all_labels_seen.add(t.get('label'))
def split_ids_labels(raw_list):
ids_out, labels_out = [], []
for tok in raw_list:
if tok in all_tab_ids or re.match(r"^[a-z_]+(_[a-z0-9]+)*$", tok):
# Heuristic: looks like an id (e.g., syllabus, pages, context_external_tool_123)
ids_out.append(tok)
elif tok in all_labels_seen:
labels_out.append(tok)
else:
# Default to label if not recognizable as id
labels_out.append(tok)
return [str(x) for x in ids_out], labels_out
if hide_src:
if os.path.exists(hide_src):
toks = read_ids_from_path(hide_src)
hide_labels = read_labels_from_path(hide_src)
elif os.path.exists(os.path.join('cache', hide_src)):
toks = read_ids_from_path(os.path.join('cache', hide_src))
hide_labels = read_labels_from_path(os.path.join('cache', hide_src))
else:
toks = parse_id_list(hide_src)
hide_ids, hide_labels = split_ids_labels(toks)
hide_labels = parse_label_list(hide_src)
if show_src:
if os.path.exists(show_src):
toks = read_ids_from_path(show_src)
show_labels = read_labels_from_path(show_src)
elif os.path.exists(os.path.join('cache', show_src)):
toks = read_ids_from_path(os.path.join('cache', show_src))
show_labels = read_labels_from_path(os.path.join('cache', show_src))
else:
toks = parse_id_list(show_src)
show_ids, show_labels = split_ids_labels(toks)
show_labels = parse_label_list(show_src)
if not (hide_ids or show_ids or hide_labels or show_labels):
print("No ids provided. Skipping Pass 2.")
# De-duplicate while keeping order roughly intact
hide_labels = list(dict.fromkeys(hide_labels))
show_labels = list(dict.fromkeys(show_labels))
if not (hide_labels or show_labels):
print("No labels provided. Skipping Pass 2.")
return
print(f"HIDE ids: {hide_ids}")
print(f"HIDE labels: {hide_labels}")
print(f"SHOW ids: {show_ids}")
print(f"SHOW labels: {show_labels}")
confirm = input("Proceed with updates? (y/N): ").strip().lower() in ['y', 'yes']
if not confirm:
print("Aborted. No changes applied.")
return
# Build lookup of labels currently ON per course from the CSV summary (to avoid unnecessary hide calls)
on_labels_by_course = defaultdict(set)
try:
csv_path = f'cache/course_nav_summary_{SEM}.csv'
with codecs.open(csv_path, 'r', 'utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
cid = str(row.get('id', '')).strip()
lbl = row.get('label')
if cid and lbl:
on_labels_by_course[cid].add(lbl)
except Exception as ex:
print(f"Warning: could not read CSV summary for ON labels: {ex}")
# Apply changes
for C in courses_in_term:
cid = str(C['id'])
@ -2638,7 +2661,6 @@ def clean_course_nav_setup_semester(section=0):
# Refresh tabs to reduce staleness just before applying
if course_tabs is None:
course_tabs = fetch(f"{url}/api/v1/courses/{cid}/tabs") or []
tabs_by_id = {str(t.get('id')): t for t in course_tabs}
tabs_by_label = defaultdict(list)
for t in course_tabs:
lbl = t.get('label')
@ -2653,18 +2675,9 @@ def clean_course_nav_setup_semester(section=0):
action = 'HIDE' if hidden_val else 'SHOW'
print(f"{action} {cid} {C.get('name','')} -> {tab_id}: {r.status_code}")
# Hide by ids
for tid in hide_ids:
if tid in tabs_by_id:
set_hidden_for_id(tid, True)
# Show by ids
for tid in show_ids:
if tid in tabs_by_id:
set_hidden_for_id(tid, False)
# Hide by labels
for lbl in hide_labels:
# Hide by labels (only if label is currently ON per CSV)
labels_to_hide_here = [lbl for lbl in hide_labels if lbl in on_labels_by_course.get(cid, set())]
for lbl in labels_to_hide_here:
for t in tabs_by_label.get(lbl, []):
tid = str(t.get('id'))
set_hidden_for_id(tid, True)