Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 46 additions & 16 deletions .github/workflows/stale-assignments.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
# - 21 days total -> unassign + add help wanted
# - Issues with lifecycle/frozen are exempt
#
# "Activity" = last human comment or the assignment event -- deliberately NOT
# issue.updated_at, which is bumped by the bot's own comment and label edits and
# would reset the clock every run (so the unassign threshold was never reached).
#
# Security: only uses numeric IDs and login names from the GitHub API.
# No untrusted string input is interpolated into shell commands.

Expand Down Expand Up @@ -83,18 +87,51 @@ jobs:
continue;
}

const updatedAt = new Date(issue.updated_at);
const daysSinceUpdate = Math.floor((now - updatedAt) / (1000 * 60 * 60 * 24));
const assigneeLogins = issue.assignees.map(a => a.login);

// Measure inactivity from the last *human* activity, NOT issue.updated_at.
// updated_at is bumped by anything that touches the issue -- including this
// bot's own warning comment and label edits -- which reset the clock every
// run, so the unassign threshold was never reached. Take the most recent of:
// last non-bot comment, the assignment event for a current assignee (so a
// freshly assigned issue is not instantly stale), and issue creation time.
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
per_page: 100,
});

let lastActivity = new Date(issue.created_at);
for (const c of comments) {
if (c.user.login === 'github-actions[bot]') continue;
const t = new Date(c.created_at);
if (t > lastActivity) lastActivity = t;
}

const events = await github.paginate(github.rest.issues.listEvents, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
per_page: 100,
});
for (const e of events) {
if (e.event === 'assigned' && e.assignee && assigneeLogins.includes(e.assignee.login)) {
const t = new Date(e.created_at);
if (t > lastActivity) lastActivity = t;
}
}

const daysSinceActivity = Math.floor((now - lastActivity) / (1000 * 60 * 60 * 24));

// 21+ days -> unassign and add help wanted
if (daysSinceUpdate >= UNASSIGN_DAYS) {
if (daysSinceActivity >= UNASSIGN_DAYS) {
const names = assigneeLogins.map(l => `@${l}`).join(', ');

console.log(`#${issue.number}: unassigning ${names} (${daysSinceUpdate} days)`);
console.log(`#${issue.number}: unassigning ${names} (${daysSinceActivity} days)`);

const unassignMsg = [
`Removing assignment from ${names} after ${daysSinceUpdate} days of inactivity`,
`Removing assignment from ${names} after ${daysSinceActivity} days of inactivity`,
'(per the [assignment policy](../blob/main/CONTRIBUTING.rst#issue-assignment-policy)).',
'',
'Feel free to comment if you want to pick this back up or if someone else wants to take it.',
Expand Down Expand Up @@ -127,15 +164,8 @@ jobs:
}

// 14+ days -> warn
if (daysSinceUpdate >= WARN_DAYS) {
// Check if we already warned (avoid spamming)
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
per_page: 5,
});

if (daysSinceActivity >= WARN_DAYS) {
// Avoid spamming: skip if we already warned within the last 10 days.
const recentWarn = comments.some(c =>
c.user.login === 'github-actions[bot]' &&
c.body.includes('assignment policy') &&
Expand All @@ -149,10 +179,10 @@ jobs:

const names = assigneeLogins.map(l => `@${l}`).join(', ');

console.log(`#${issue.number}: warning ${names} (${daysSinceUpdate} days)`);
console.log(`#${issue.number}: warning ${names} (${daysSinceActivity} days)`);

const warnMsg = [
`${names}, this issue has been inactive for ${daysSinceUpdate} days.`,
`${names}, this issue has been inactive for ${daysSinceActivity} days.`,
'Are you still working on it? Drop a comment to let us know.',
'',
'If there is no update within 7 days, the assignment will be removed',
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,4 @@ Apache Burr is released under the Apache 2.0 License. See [LICENSE](https://gith
## Contributing
We're very supportive of changes by new contributors, big or small! Make sure to discuss potential changes by creating an issue or commenting on an existing one before opening a pull request. Good first contributions include creating an example or an integration with your favorite Python library!

To contribute, checkout our [contributing guidelines](https://github.com/apache/burr/blob/main/CONTRIBUTING.rst), our [developer setup guide](https://github.com/apache/burr/blob/main/developer_setup.md), and our [Code of Conduct](https://github.com/apache/burr/blob/main/CODE_OF_CONDUCT.md).
To contribute, checkout our [contributing guidelines](https://github.com/apache/burr/blob/main/CONTRIBUTING.rst), our [developer setup guide](https://github.com/apache/burr/blob/main/docs/contributing/setup.rst), and the [Code of Conduct](https://www.apache.org/foundation/policies/conduct.html).
4 changes: 4 additions & 0 deletions telemetry/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { StreamingChatbotWithTelemetry } from './examples/StreamingChatbot';
import { AdminView } from './components/routes/AdminView';
import { AnnotationsViewContainer } from './components/routes/app/AnnotationsView';
import { DeepResearcherWithTelemetry } from './examples/DeepResearcher';
import { useTheme } from './hooks/useTheme';

/**
* Basic application. We have an AppContainer -- this has a breadcrumb and a sidebar.
Expand All @@ -48,6 +49,9 @@ import { DeepResearcherWithTelemetry } from './examples/DeepResearcher';
* @returns A rendered application object
*/
const App = () => {
// Initialize theme at the app root so the `dark` class is applied on load
// (respects system preference, falls back to stored manual override).
useTheme();
return (
<QueryClientProvider client={new QueryClient()}>
<Router basename={window.__BURR_BASE_PATH__ || ''}>
Expand Down
47 changes: 47 additions & 0 deletions telemetry/ui/src/components/common/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { MoonIcon, SunIcon } from '@heroicons/react/24/outline';
import { classNames } from '../../utils/tailwind';
import { useTheme } from '../../hooks/useTheme';

/**
* A simple sun/moon button that toggles between light and dark mode.
* Replaces the previously broken radio toggle referenced in issue #209.
*/
export const ThemeToggle = (props: { showLabel?: boolean }) => {
const { isDark, toggle } = useTheme();
const Icon = isDark ? SunIcon : MoonIcon;
const label = isDark ? 'Switch to light mode' : 'Switch to dark mode';
return (
<button
type="button"
onClick={toggle}
title={label}
aria-label={label}
className={classNames(
'group flex items-center gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold',
'text-gray-700 hover:bg-gray-50 hover:text-dwdarkblue',
'dark:text-gray-200 dark:hover:bg-gray-800 dark:hover:text-white'
)}>
<Icon className="h-6 w-6 shrink-0 text-gray-400 group-hover:text-dwdarkblue dark:group-hover:text-white" />
{props.showLabel && <span>{isDark ? 'Light mode' : 'Dark mode'}</span>}
</button>
);
};
36 changes: 19 additions & 17 deletions telemetry/ui/src/components/nav/appcontainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { classNames } from '../../utils/tailwind';
import React from 'react';
import { DefaultService } from '../../api';
import { useQuery } from 'react-query';
import { ThemeToggle } from '../common/ThemeToggle';

// Define your GitHub logo SVG as a React component
const GithubLogo = () => (
Expand Down Expand Up @@ -64,8 +65,8 @@ const ToggleOpenButton = (props: { open: boolean; toggleSidebar: () => void }) =
return (
<MinimizeMaximizeIcon
className={classNames(
'text-gray-400',
'h-8 w-8 hover:bg-gray-50 rounded-md hover:cursor-pointer'
'text-gray-400 dark:text-gray-500',
'h-8 w-8 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md hover:cursor-pointer'
)}
aria-hidden="true"
onClick={props.toggleSidebar}
Expand Down Expand Up @@ -181,7 +182,7 @@ export const AppContainer = (props: { children: React.ReactNode }) => {

return (
<>
<div className="h-screen w-screen overflow-x-auto">
<div className="h-screen w-screen overflow-x-auto bg-white dark:bg-gray-900">
<Transition.Root show={smallSidebarOpen} as={Fragment}>
<Dialog as="div" className="relative z-50 lg:hidden" onClose={setSmallSidebarOpen}>
<Transition.Child
Expand Down Expand Up @@ -224,7 +225,7 @@ export const AppContainer = (props: { children: React.ReactNode }) => {
</div>
</Transition.Child>
{/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex grow flex-col gap-y-5 overflow-y-auto bg-white px-6 pb-2 py-2">
<div className="flex grow flex-col gap-y-5 overflow-y-auto bg-white px-6 pb-2 py-2 dark:bg-gray-900">
<div className="flex h-16 shrink-0 items-center">
<img
className="h-10 w-auto"
Expand Down Expand Up @@ -280,7 +281,7 @@ export const AppContainer = (props: { children: React.ReactNode }) => {
sidebarOpen ? 'h-screen lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col' : ''
}`}>
{/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6 py-2">
<div className="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6 py-2 dark:border-gray-700 dark:bg-gray-900">
<div className="flex h-16 shrink-0 items-center">
<img
className="h-12 w-auto"
Expand All @@ -299,14 +300,14 @@ export const AppContainer = (props: { children: React.ReactNode }) => {
to={item.href}
className={classNames(
isCurrent(item.href, item.linkType)
? 'bg-gray-50'
: 'hover:bg-gray-50',
'group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold text-gray-700'
? 'bg-gray-50 dark:bg-gray-800'
: 'hover:bg-gray-50 dark:hover:bg-gray-800',
'group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold text-gray-700 dark:text-gray-200'
)}
target={item.linkType === 'external' ? '_blank' : undefined}
rel={item.linkType === 'external' ? 'noreferrer' : undefined}>
<item.icon
className="h-6 w-6 shrink-0 text-gray-400"
className="h-6 w-6 shrink-0 text-gray-400 dark:text-gray-500"
aria-hidden="true"
/>
{item.name}
Expand All @@ -318,12 +319,12 @@ export const AppContainer = (props: { children: React.ReactNode }) => {
<Disclosure.Button
className={classNames(
isCurrent(item.href, item.linkType)
? 'bg-gray-50'
: 'hover:bg-gray-50',
'flex items-center w-full text-left rounded-md p-2 gap-x-3 text-sm leading-6 font-semibold text-gray-700'
? 'bg-gray-50 dark:bg-gray-800'
: 'hover:bg-gray-50 dark:hover:bg-gray-800',
'flex items-center w-full text-left rounded-md p-2 gap-x-3 text-sm leading-6 font-semibold text-gray-700 dark:text-gray-200'
)}>
<item.icon
className="h-6 w-6 shrink-0 text-gray-400"
className="h-6 w-6 shrink-0 text-gray-400 dark:text-gray-500"
aria-hidden="true"
/>
{item.name}
Expand All @@ -342,9 +343,9 @@ export const AppContainer = (props: { children: React.ReactNode }) => {
to={subItem.href}
className={classNames(
isCurrent(subItem.href, subItem.linkType)
? 'bg-gray-50'
: 'hover:bg-gray-50',
'block rounded-md py-2 pr-2 pl-9 text-sm leading-6 text-gray-700'
? 'bg-gray-50 dark:bg-gray-800'
: 'hover:bg-gray-50 dark:hover:bg-gray-800',
'block rounded-md py-2 pr-2 pl-9 text-sm leading-6 text-gray-700 dark:text-gray-300'
)}
target={
subItem.linkType === 'external' ? '_blank' : undefined
Expand All @@ -367,7 +368,8 @@ export const AppContainer = (props: { children: React.ReactNode }) => {
</li>
</ul>
</nav>
<div className="flex justify-start -mx-5">
<div className="flex justify-between items-center -mx-5 px-5">
<ThemeToggle />
<ToggleOpenButton open={sidebarOpen} toggleSidebar={toggleSidebar} />
</div>
</div>
Expand Down
6 changes: 4 additions & 2 deletions telemetry/ui/src/components/nav/breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ export const BreadCrumb = () => {
<path d="M5.555 17.776l8-16 .894.448-8 16-.894-.448z" />
</svg>
{isNullPK ? (
<span className="ml-4 text-sm font-medium text-gray-200">no primary key</span>
<span className="ml-4 text-sm font-medium text-gray-200 dark:text-gray-600">
no primary key
</span>
) : (
<Link
to={page.href}
className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700"
className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
aria-current={page.current ? 'page' : undefined}
>
{decodeURIComponent(page.name)}
Expand Down
4 changes: 2 additions & 2 deletions telemetry/ui/src/components/routes/ProjectList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ export const ProjectListTable = (props: { projects: Project[]; includeAnnotation
return (
<TableRow
key={project.id}
className="hover:bg-gray-50 cursor-pointer"
className="hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer"
onClick={() => navigate(`/project/${project.id}`)}
>
<TableCell className="font-semibold text-gray-700">
<TableCell className="font-semibold text-gray-700 dark:text-gray-200">
<div className="flex flex-row gap-2">
{chipType !== undefined && <Chip label={chipType} chipType={chipType}></Chip>}
{projectName}
Expand Down
Loading
Loading