Repository: web3infra-foundation/mega (language: rust)

Issue:
[Moon UI] Improve Commit Message Display After Pushing Repos To Third-Part
Currently, when pushing a **third-party** repository to the monorepo, a new commit is created and attached to the monorepo tree with a default message like “push third-part crates xxx commit.” 
This is not ideal, as it doesn’t reflect the actual changes. 
Instead, the commit message should be replaced with the latest commit message from the pushed repository to provide more accurate and meaningful information.


Discussion / hints:

Fix patch:
diff --git a/ceres/src/api_service/import_api_service.rs b/ceres/src/api_service/import_api_service.rs
index 9f9b132d..2af2b778 100644
--- a/ceres/src/api_service/import_api_service.rs
+++ b/ceres/src/api_service/import_api_service.rs
@@ -150,7 +150,7 @@ impl ApiHandler for ImportApiService {
         &self,
         path: &Path,
         start_commit: Commit,
-        target: TreeItem,
+        target: &TreeItem,
     ) -> Commit {
         let mut target_commit = start_commit.clone();
         let mut visited = HashSet::new();
@@ -162,7 +162,7 @@ impl ApiHandler for ImportApiService {
         while let Some(commit) = p_stack.pop_front() {
             let root_tree = self.get_tree_by_hash(&commit.tree_id.to_plain_str()).await;
             let reachable = self
-                .reachable_in_tree(&root_tree, path, target.clone())
+                .reachable_in_tree(&root_tree, path, target)
                 .await
                 .unwrap();
             if reachable {
diff --git a/ceres/src/api_service/mod.rs b/ceres/src/api_service/mod.rs
index 4f8e454e..206fe7d1 100644
--- a/ceres/src/api_service/mod.rs
+++ b/ceres/src/api_service/mod.rs
@@ -76,7 +76,7 @@ pub trait ApiHandler: Send + Sync {
         &self,
         path: &Path,
         commit: Commit,
-        target: TreeItem,
+        target: &TreeItem,
     ) -> Commit;
 
     async fn get_blob_as_string(&self, file_path: PathBuf) -> Result<String, GitError> {
@@ -166,9 +166,9 @@ pub trait ApiHandler: Send + Sync {
                         let commit = if let Some(commit) = commit_map.get(commit_id) {
                             commit
                         } else {
-                            tracing::error!("failed fecth commit: {}", commit_id);
+                            tracing::warn!("failed fecth commit: {}", commit_id);
                             &self
-                                .traverse_commit_history(&path, self.get_root_commit().await, item)
+                                .traverse_commit_history(&path, self.get_root_commit().await, &item)
                                 .await
                         };
                         info.oid = commit.id.to_plain_str();
@@ -177,6 +177,12 @@ pub trait ApiHandler: Send + Sync {
                     }
                     items.push(info);
                 }
+                // sort with type and date
+                items.sort_by(|a, b| {
+                    a.content_type
+                        .cmp(&b.content_type)
+                        .then(b.date.cmp(&a.date))
+                });
                 Ok(items)
             }
             None => Ok(Vec::new()),
@@ -385,7 +391,7 @@ pub trait ApiHandler: Send + Sync {
         &self,
         root_tree: &Tree,
         path: &Path,
-        target: TreeItem,
+        target: &TreeItem,
     ) -> Result<bool, GitError> {
         let relative_path = self.strip_relative(path).unwrap();
         let mut search_tree = root_tree.clone();
@@ -406,7 +412,7 @@ pub trait ApiHandler: Send + Sync {
             }
         }
         // check item exist under search tree
-        if search_tree.tree_items.into_iter().any(|x| x == target) {
+        if search_tree.tree_items.iter().any(|x| x == target) {
             return Ok(true);
         }
         Ok(false)
diff --git a/ceres/src/api_service/mono_api_service.rs b/ceres/src/api_service/mono_api_service.rs
index 237b953d..889c9db1 100644
--- a/ceres/src/api_service/mono_api_service.rs
+++ b/ceres/src/api_service/mono_api_service.rs
@@ -190,7 +190,7 @@ impl ApiHandler for MonoApiService {
         Ok(commits.into_iter().map(|x| x.into()).collect())
     }
 
-    async fn traverse_commit_history(&self, _: &Path, _: Commit, _: TreeItem) -> Commit {
+    async fn traverse_commit_history(&self, _: &Path, _: Commit, _: &TreeItem) -> Commit {
         unreachable!()
     }
 }
diff --git a/ceres/src/model/mr.rs b/ceres/src/model/mr.rs
index 3fa4e597..c02228fa 100644
--- a/ceres/src/model/mr.rs
+++ b/ceres/src/model/mr.rs
@@ -9,6 +9,7 @@ pub struct MrInfoItem {
     pub status: String,
     pub open_timestamp: i64,
     pub merge_timestamp: Option<i64>,
+    pub updated_at: i64,
 }
 
 impl From<mega_mr::Model> for MrInfoItem {
@@ -19,6 +20,7 @@ impl From<mega_mr::Model> for MrInfoItem {
             status: value.status.to_string(),
             open_timestamp: value.created_at.and_utc().timestamp(),
             merge_timestamp: value.merge_date.map(|dt| dt.and_utc().timestamp()),
+            updated_at: value.updated_at.and_utc().timestamp(),
         }
     }
 }
diff --git a/ceres/src/pack/import_repo.rs b/ceres/src/pack/import_repo.rs
index eba8f3ba..38eae2ce 100644
--- a/ceres/src/pack/import_repo.rs
+++ b/ceres/src/pack/import_repo.rs
@@ -15,10 +15,7 @@ use tokio_stream::wrappers::ReceiverStream;
 
 use callisto::{mega_tree, raw_blob};
 use common::errors::MegaError;
-use jupiter::{
-    context::Context,
-    storage::batch_save_model,
-};
+use jupiter::{context::Context, storage::batch_save_model};
 use mercury::{
     errors::GitError,
     internal::{
@@ -30,12 +27,17 @@ use mercury::{hash::SHA1, internal::pack::encode::PackEncoder};
 
 use crate::{
     api_service::{mono_api_service::MonoApiService, ApiHandler},
-    pack::PackHandler, protocol::{import_refs::{CommandType, RefCommand, Refs}, repo::Repo},
+    pack::PackHandler,
+    protocol::{
+        import_refs::{CommandType, RefCommand, Refs},
+        repo::Repo,
+    },
 };
 
 pub struct ImportRepo {
     pub context: Context,
     pub repo: Repo,
+    pub command_list: Vec<RefCommand>,
 }
 
 #[async_trait]
@@ -54,7 +56,6 @@ impl PackHandler for ImportRepo {
     }
 
     async fn handle_receiver(&self, receiver: Receiver<Entry>) -> Result<(), GitError> {
-        self.create_monorepo_parent().await.unwrap();
         let storage = self.context.services.git_db_storage.clone();
         let mut entry_list = vec![];
         let mut join_tasks = vec![];
@@ -64,14 +65,21 @@ impl PackHandler for ImportRepo {
                 let stg_clone = storage.clone();
                 let repo_clone = self.repo.clone();
                 let handle = tokio::spawn(async move {
-                    stg_clone.save_entry(repo_clone.repo_id, entry_list).await.unwrap();
+                    stg_clone
+                        .save_entry(repo_clone.repo_id, entry_list)
+                        .await
+                        .unwrap();
                 });
                 join_tasks.push(handle);
                 entry_list = vec![];
             }
         }
         join_all(join_tasks).await;
-        storage.save_entry(self.repo.repo_id, entry_list).await.unwrap();
+        storage
+            .save_entry(self.repo.repo_id, entry_list)
+            .await
+            .unwrap();
+        self.attach_to_monorepo_parent().await.unwrap();
         Ok(())
     }
 
@@ -130,7 +138,8 @@ impl PackHandler for ImportRepo {
                 let sender_clone = entry_tx.clone();
                 let chunk_clone = chunk.to_vec();
                 let handler = tokio::spawn(async move {
-                    let mut blob_stream = raw_storage.get_raw_blobs_stream(chunk_clone).await.unwrap();
+                    let mut blob_stream =
+                        raw_storage.get_raw_blobs_stream(chunk_clone).await.unwrap();
                     while let Some(model) = blob_stream.next().await {
                         match model {
                             Ok(m) => {
@@ -289,9 +298,15 @@ impl PackHandler for ImportRepo {
         let storage = self.context.services.git_db_storage.clone();
         match refs.command_type {
             CommandType::Create => {
-                storage.save_ref(self.repo.repo_id, refs.clone().into()).await.unwrap();
+                storage
+                    .save_ref(self.repo.repo_id, refs.clone().into())
+                    .await
+                    .unwrap();
             }
-            CommandType::Delete => storage.remove_ref(self.repo.repo_id, &refs.ref_name).await.unwrap(),
+            CommandType::Delete => storage
+                .remove_ref(self.repo.repo_id, &refs.ref_name)
+                .await
+                .unwrap(),
             CommandType::Update => {
                 storage
                     .update_ref(self.repo.repo_id, &refs.ref_name, &refs.new_id)
@@ -314,13 +329,16 @@ impl PackHandler for ImportRepo {
 
     async fn check_default_branch(&self) -> bool {
         let storage = self.context.services.git_db_storage.clone();
-        storage.default_branch_exist(self.repo.repo_id).await.unwrap()
+        storage
+            .default_branch_exist(self.repo.repo_id)
+            .await
+            .unwrap()
     }
 }
 
 impl ImportRepo {
-    // create monorepo parent for preserve import repo
-    async fn create_monorepo_parent(&self) -> Result<(), GitError> {
+    // attach import repo to monorepo parent tree
+    async fn attach_to_monorepo_parent(&self) -> Result<(), GitError> {
         let path = PathBuf::from(self.repo.repo_path.clone());
         let mono_api_service = MonoApiService {
             context: self.context.clone(),
@@ -329,13 +347,21 @@ impl ImportRepo {
         let save_trees = mono_api_service.search_and_create_tree(&path).await?;
 
         let mut root_ref = storage.get_ref("/").await.unwrap().unwrap();
+        let commit_id = &self.command_list.first().unwrap().new_id;
+        let latest_commit: Commit = self
+            .context
+            .services
+            .git_db_storage
+            .get_commit_by_hash(self.repo.repo_id, commit_id)
+            .await
+            .unwrap()
+            .unwrap()
+            .into();
+        let commit_msg = latest_commit.format_message();
         let new_commit = Commit::from_tree_id(
             save_trees.back().unwrap().id,
             vec![SHA1::from_str(&root_ref.ref_commit_hash).unwrap()],
-            &format!(
-                "push thrid-part crates {:?} commit",
-                path.file_name().unwrap()
-            ),
+            &commit_msg,
         );
 
         let save_trees: Vec<mega_tree::ActiveModel> = save_trees
diff --git a/ceres/src/protocol/mod.rs b/ceres/src/protocol/mod.rs
index 4200b6d2..9299d6ba 100644
--- a/ceres/src/protocol/mod.rs
+++ b/ceres/src/protocol/mod.rs
@@ -172,6 +172,7 @@ impl SmartProtocol {
             Ok(Arc::new(ImportRepo {
                 context: self.context.clone(),
                 repo,
+                command_list: self.command_list.clone(),
             }))
         } else {
             let mut res = MonoRepo {
diff --git a/mono/src/api/oauth/model.rs b/mono/src/api/oauth/model.rs
index b0d7384b..b2011d3d 100644
--- a/mono/src/api/oauth/model.rs
+++ b/mono/src/api/oauth/model.rs
@@ -22,7 +22,8 @@ pub struct GitHubUserJson {
     pub login: String,
     pub id: u32,
     pub avatar_url: String,
-    pub email: String,
+    // email can be null from github
+    pub email: Option<String>,
 }
 
 impl From<GitHubUserJson> for user::Model {
@@ -30,7 +31,7 @@ impl From<GitHubUserJson> for user::Model {
         Self {
             id: generate_id(),
             name: value.login,
-            email: value.email,
+            email: value.email.unwrap_or_default(),
             avatar_url: value.avatar_url,
             is_github: true,
             created_at: chrono::Utc::now().naive_utc(),
diff --git a/moon/src/app/(dashboard)/application-layout.tsx b/moon/src/app/(dashboard)/application-layout.tsx
index 8d903a0c..fc98b0b3 100644
--- a/moon/src/app/(dashboard)/application-layout.tsx
+++ b/moon/src/app/(dashboard)/application-layout.tsx
@@ -124,28 +124,7 @@ export function ApplicationLayout({
               <DropdownButton as={SidebarItem}>
                 <Avatar src="/images/megaLogo.png" />
                 <SidebarLabel>Mega</SidebarLabel>
-                <ChevronDownIcon />
               </DropdownButton>
-              <DropdownMenu className="min-w-80 lg:min-w-64" anchor="bottom start">
-                <DropdownItem href="/settings">
-                  <Cog8ToothIcon />
-                  <DropdownLabel>Settings</DropdownLabel>
-                </DropdownItem>
-                <DropdownDivider />
-                <DropdownItem href="#">
-                  <Avatar slot="icon" initials="AD" className="bg-purple-500 text-white" />
-                  <DropdownLabel>Admin</DropdownLabel>
-                </DropdownItem>
-                <DropdownItem href="#">
-                  <Avatar slot="icon" initials="BE" className="bg-purple-500 text-white" />
-                  <DropdownLabel>Big Events</DropdownLabel>
-                </DropdownItem>
-                <DropdownDivider />
-                <DropdownItem href="#">
-                  <PlusIcon />
-                  <DropdownLabel>New team&hellip;</DropdownLabel>
-                </DropdownItem>
-              </DropdownMenu>
             </Dropdown>
           </SidebarHeader>
 
@@ -163,19 +142,15 @@ export function ApplicationLayout({
                 <TicketIcon />
                 <SidebarLabel>Merge Request</SidebarLabel>
               </SidebarItem>
-              <SidebarItem href="/settings" current={pathname.startsWith('/settings')}>
-                <Cog6ToothIcon />
-                <SidebarLabel>Settings</SidebarLabel>
-              </SidebarItem>
             </SidebarSection>
             <SidebarSpacer />
 
             <SidebarSection>
-              <SidebarItem href="#">
+              <SidebarItem href="/support">
                 <QuestionMarkCircleIcon />
                 <SidebarLabel>Support</SidebarLabel>
               </SidebarItem>
-              <SidebarItem href="#">
+              <SidebarItem href="/changelog">
                 <SparklesIcon />
                 <SidebarLabel>Changelog</SidebarLabel>
               </SidebarItem>
@@ -187,7 +162,7 @@ export function ApplicationLayout({
               <Dropdown>
                 <DropdownButton as={SidebarItem}>
                   <span className="flex min-w-0 items-center gap-3">
-                    <Avatar src={user.avatar_url} slot="icon" initials="ME" className="size-10 bg-purple-500 text-white" />
+                    <Avatar src={user.avatar_url} slot="icon" className="size-10  text-white" />
                     <span className="min-w-0">
                       <span className="block truncate text-sm/5 font-medium text-zinc-950 dark:text-white">{user.name}</span>
                       <span className="block truncate text-xs/5 font-normal text-zinc-500 dark:text-zinc-400">
diff --git a/moon/src/app/(dashboard)/changelog/page.tsx b/moon/src/app/(dashboard)/changelog/page.tsx
new file mode 100644
index 00000000..0cd3f998
--- /dev/null
+++ b/moon/src/app/(dashboard)/changelog/page.tsx
@@ -0,0 +1,8 @@
+export default function ChangeLogPage() {
+
+    return (
+        <div>
+            ChangeLog Page
+        </div>
+    )
+}
\ No newline at end of file
diff --git a/moon/src/app/(dashboard)/issue/page.tsx b/moon/src/app/(dashboard)/issue/page.tsx
new file mode 100644
index 00000000..2a97c2f9
--- /dev/null
+++ b/moon/src/app/(dashboard)/issue/page.tsx
@@ -0,0 +1,8 @@
+export default function IssuePage() {
+
+    return (
+        <div>
+            Issue Page
+        </div>
+    )
+}
\ No newline at end of file
diff --git a/moon/src/app/(dashboard)/support/page.tsx b/moon/src/app/(dashboard)/support/page.tsx
new file mode 100644
index 00000000..d448014a
--- /dev/null
+++ b/moon/src/app/(dashboard)/support/page.tsx
@@ -0,0 +1,8 @@
+export default function SupportPage() {
+
+    return (
+        <div>
+            Support Page
+        </div>
+    )
+}
\ No newline at end of file
diff --git a/moon/src/app/(dashboard)/user/keys/page.tsx b/moon/src/app/(dashboard)/user/keys/page.tsx
index 28326761..de55c436 100644
--- a/moon/src/app/(dashboard)/user/keys/page.tsx
+++ b/moon/src/app/(dashboard)/user/keys/page.tsx
@@ -43,8 +43,8 @@ export default function KeysPage() {
             console.error('Error fetching data:', error);
         }
     };
-    useEffect(() => {
 
+    useEffect(() => {
         fetchData();
     }, []);
 
diff --git a/moon/src/components/CodeTable.tsx b/moon/src/components/CodeTable.tsx
index ab871b1f..266ebeb8 100644
--- a/moon/src/components/CodeTable.tsx
+++ b/moon/src/components/CodeTable.tsx
@@ -36,17 +36,11 @@ const CodeTable = ({ directory, readmeContent }) => {
             key: 'name',
             render: (_, record) => {
                 return <>
-                    {record.content_type === "file" &&
-                        <Space>
-                            <DocumentIcon className="size-6" />
-                            <span onClick={() => handleFileClick(record)}>{record.name}</span>
-                        </Space>
-                    }
-                    {record.content_type === "directory" &&
-                        <Space>
-                            <FolderIcon className="size-6" />
-                            <a onClick={() => handleDirectoryClick(record)}>{record.name}</a>
-                        </Space>}
+                    <Space>
+                        {record.content_type === "directory" && <FolderIcon className="size-6" />}
+                        {record.content_type === "file" && <DocumentIcon className="size-6" />}
+                        <a>{record.name}</a>
+                    </Space>
                 </>
             }
         },
@@ -67,23 +61,22 @@ const CodeTable = ({ directory, readmeContent }) => {
             )
         }
     ];
-
-    const handleFileClick = (file) => {
-        const newPath = `/blob/${real_path}/${file.name}`;
-        router.push(newPath);
-    };
-
-    const handleDirectoryClick = async (directory) => {
-        var newPath = '';
-        if (real_path === '/') {
-            newPath = `/tree/${directory.name}`;
+    const handleRowClick = (record) => {
+        if (record.content_type === "file") {
+            const newPath = `/blob/${real_path}/${record.name}`;
+            router.push(newPath);
         } else {
-            newPath = `/tree/${real_path}/${directory.name}`;
+            var newPath = '';
+            if (real_path === '/') {
+                newPath = `/tree/${record.name}`;
+            } else {
+                newPath = `/tree/${real_path}/${record.name}`;
+            }
+            router.push(
+                newPath,
+            );
         }
-        router.push(
-            newPath,
-        );
-    };
+    }
 
     const handleGoBack = () => {
         const safePath = real_path.split('/');
@@ -95,21 +88,17 @@ const CodeTable = ({ directory, readmeContent }) => {
         }
     };
 
-    // sort by file type, render folder type first
-    const sortedDir = directory.sort((a, b) => {
-        if (a.content_type === 'directory' && b.content_type === 'file') {
-            return -1;
-        } else if (a.content_type === 'file' && b.content_type === 'directory') {
-            return 1;
-        } else {
-            return 0;
-        }
-    });
-
-
     return (
         <div style={fileCodeContainerStyle}>
-            <Table style={{ clear: "none" }} rowClassName={styles.dirShowTr} pagination={false} columns={columns} dataSource={sortedDir} />
+            <Table style={{ clear: "none" }} rowClassName={styles.dirShowTr}
+                pagination={false} columns={columns}
+                dataSource={directory} rowKey="oid"
+                onRow={(record) => {
+                    return {
+                        onClick: (event) => { handleRowClick(record) }
+                    };
+                }}
+            />
             {readmeContent && (
                 <div className={styles.markdownContent}>
                     <div className="markdown-body">
diff --git a/moon/src/components/MergeList.tsx b/moon/src/components/MergeList.tsx
index 84fec655..863bc816 100644
--- a/moon/src/components/MergeList.tsx
+++ b/moon/src/components/MergeList.tsx
@@ -10,6 +10,7 @@ interface MrInfoItem {
     status: string,
     open_timestamp: number,
     merge_timestamp: number | null,
+    updated_at: number,
 }
 
 interface MergeListProps {
@@ -25,7 +26,7 @@ const MergeList: React.FC<MergeListProps> = ({ mrList }) => {
             case 'merged':
                 return <Tag color="purple">merged</Tag>;
             case 'closed':
-                return <Tag color="failed">closed</Tag>;
+                return <Tag color="error">closed</Tag>;
         }
     };
 
@@ -40,7 +41,7 @@ const MergeList: React.FC<MergeListProps> = ({ mrList }) => {
                     return "";
                 }
             case 'closed':
-                return <Tag color="failed">closed</Tag>;
+                return (`MR ${item.mr_link} by Admin was closed ${formatDistance(fromUnixTime(item.updated_at), new Date(), { addSuffix: true })}` )
         }
     }
 
diff --git a/moon/src/components/catalyst/sidebar-layout.tsx b/moon/src/components/catalyst/sidebar-layout.tsx
index 4b7677e6..d326e050 100644
--- a/moon/src/components/catalyst/sidebar-layout.tsx
+++ b/moon/src/components/catalyst/sidebar-layout.tsx
@@ -74,7 +74,7 @@ export function SidebarLayout({
       {/* Content */}
       <main className="flex flex-1 flex-col pb-2 lg:min-w-0 lg:pl-64 lg:pr-2 lg:pt-2">
         <div className="grow p-6 lg:rounded-lg lg:bg-white lg:p-10 lg:shadow-sm lg:ring-1 lg:ring-zinc-950/5 dark:lg:bg-zinc-900 dark:lg:ring-white/10">
-          <div className="mx-auto max-w-6xl">{children}</div>
+          <div className="mx-auto">{children}</div>
         </div>
       </main>
     </div>


Test patch: