fix(home): missing key and invalid dates in Recents cards (#13291)

diff --git a/superset-frontend/src/types/Chart.ts b/superset-frontend/src/types/Chart.ts
index 5148d32..cf78dab 100644
--- a/superset-frontend/src/types/Chart.ts
+++ b/superset-frontend/src/types/Chart.ts
@@ -23,17 +23,18 @@
 
 import Owner from './Owner';
 
-export default interface Chart {
+export interface Chart {
   id: number;
   url: string;
   viz_type: string;
   slice_name: string;
   creator: string;
   changed_on: string;
+  changed_on_delta_humanized?: string;
+  changed_on_utc?: string;
   description: string | null;
   cache_timeout: number | null;
   thumbnail_url?: string;
-  changed_on_delta_humanized?: string;
   owners?: Owner[];
   datasource_name_text?: string;
 }
@@ -44,4 +45,7 @@
   slice_name: string;
   description: string | null;
   cache_timeout: number | null;
+  url?: string;
 };
+
+export default Chart;
diff --git a/superset-frontend/src/views/CRUD/types.ts b/superset-frontend/src/views/CRUD/types.ts
index 4a9a2c5..8e54bbd 100644
--- a/superset-frontend/src/views/CRUD/types.ts
+++ b/superset-frontend/src/views/CRUD/types.ts
@@ -34,7 +34,8 @@
 export interface Dashboard {
   changed_by_name: string;
   changed_by_url: string;
-  changed_on_delta_humanized: string;
+  changed_on_delta_humanized?: string;
+  changed_on_utc?: string;
   changed_by: string;
   dashboard_title: string;
   slice_name?: string;
@@ -47,13 +48,15 @@
 }
 
 export type SavedQueryObject = {
+  id: number;
+  changed_on: string;
+  changed_on_delta_humanized: string;
   database: {
     database_name: string;
     id: number;
   };
   db_id: number;
   description?: string;
-  id: number;
   label: string;
   schema: string;
   sql: string | null;
diff --git a/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx b/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx
index 5e5cc13..b52dfe2 100644
--- a/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx
+++ b/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx
@@ -23,25 +23,43 @@
 import Loading from 'src/components/Loading';
 import ListViewCard from 'src/components/ListViewCard';
 import SubMenu from 'src/components/Menu/SubMenu';
+import { Chart } from 'src/types/Chart';
+import { Dashboard, SavedQueryObject } from 'src/views/CRUD/types';
+import { mq, CardStyles } from 'src/views/CRUD/utils';
+
 import { ActivityData } from './Welcome';
-import { mq, CardStyles } from '../utils';
 import EmptyState from './EmptyState';
 
-interface ActivityObjects {
-  action?: string;
-  item_title?: string;
-  slice_name: string;
-  time: string;
-  changed_on_utc: string;
-  url: string;
-  sql: string;
-  dashboard_title: string;
-  label: string;
-  id: string;
-  table: object;
+/**
+ * Return result from /superset/recent_activity/{user_id}
+ */
+interface RecentActivity {
+  action: string;
+  item_type: 'slice' | 'dashboard';
   item_url: string;
+  item_title: string;
+  time: number;
+  time_delta_humanized?: string;
 }
 
+interface RecentSlice extends RecentActivity {
+  item_type: 'slice';
+}
+
+interface RecentDashboard extends RecentActivity {
+  item_type: 'dashboard';
+}
+
+/**
+ * Recent activity objects fetched by `getRecentAcitivtyObjs`.
+ */
+type ActivityObject =
+  | RecentSlice
+  | RecentDashboard
+  | Chart
+  | Dashboard
+  | SavedQueryObject;
+
 interface ActivityProps {
   user: {
     userId: string | number;
@@ -79,31 +97,70 @@
   }
 `;
 
+const UNTITLED = t('[Untitled]');
+const UNKNOWN_TIME = t('Unknown');
+
+const getEntityTitle = (entity: ActivityObject) => {
+  if ('dashboard_title' in entity) return entity.dashboard_title || UNTITLED;
+  if ('slice_name' in entity) return entity.slice_name || UNTITLED;
+  if ('label' in entity) return entity.label || UNTITLED;
+  return entity.item_title || UNTITLED;
+};
+
+const getEntityIconName = (entity: ActivityObject) => {
+  if ('sql' in entity) return 'sql';
+  const url = 'item_url' in entity ? entity.item_url : entity.url;
+  if (url?.includes('dashboard')) {
+    return 'nav-dashboard';
+  }
+  if (url?.includes('explore')) {
+    return 'nav-charts';
+  }
+  return '';
+};
+
+const getEntityUrl = (entity: ActivityObject) => {
+  if ('sql' in entity) return `/superset/sqllab?savedQueryId=${entity.id}`;
+  if ('url' in entity) return entity.url;
+  return entity.item_url;
+};
+
+const getEntityLastActionOn = (entity: ActivityObject) => {
+  // translation keys for last action on
+  const LAST_VIEWED = `Last viewed %s`;
+  const LAST_MODIFIED = `Last modified %s`;
+
+  // for Recent viewed items
+  if ('time_delta_humanized' in entity) {
+    return t(LAST_VIEWED, entity.time_delta_humanized);
+  }
+
+  if ('changed_on_delta_humanized' in entity) {
+    return t(LAST_MODIFIED, entity.changed_on_delta_humanized);
+  }
+
+  let time: number | string | undefined | null;
+  let translationKey = LAST_MODIFIED;
+  if ('time' in entity) {
+    // eslint-disable-next-line prefer-destructuring
+    time = entity.time;
+    translationKey = LAST_VIEWED;
+  }
+  if ('changed_on' in entity) time = entity.changed_on;
+  if ('changed_on_utc' in entity) time = entity.changed_on_utc;
+
+  return t(
+    translationKey,
+    time == null ? UNKNOWN_TIME : moment(time).fromNow(),
+  );
+};
+
 export default function ActivityTable({
   loading,
   activeChild,
   setActiveChild,
   activityData,
 }: ActivityProps) {
-  const getFilterTitle = (e: ActivityObjects) => {
-    if (e.dashboard_title) return e.dashboard_title;
-    if (e.label) return e.label;
-    if (e.url && !e.table) return e.item_title;
-    if (e.item_title) return e.item_title;
-    return e.slice_name;
-  };
-
-  const getIconName = (e: ActivityObjects) => {
-    if (e.sql) return 'sql';
-    if (e.url?.includes('dashboard') || e.item_url?.includes('dashboard')) {
-      return 'nav-dashboard';
-    }
-    if (e.url?.includes('explore') || e.item_url?.includes('explore')) {
-      return 'nav-charts';
-    }
-    return '';
-  };
-
   const tabs = [
     {
       name: 'Edited',
@@ -139,35 +196,30 @@
     });
   }
 
-  const renderActivity = () => {
-    const getRecentRef = (e: ActivityObjects) => {
-      if (activeChild === 'Viewed') {
-        return e.item_url;
-      }
-      return e.sql ? `/superset/sqllab?savedQueryId=${e.id}` : e.url;
-    };
-    return activityData[activeChild].map((e: ActivityObjects) => (
-      <CardStyles
-        onClick={() => {
-          window.location.href = getRecentRef(e);
-        }}
-        key={e.id}
-      >
-        <ListViewCard
-          loading={loading}
-          cover={<></>}
-          url={e.sql ? `/superset/sqllab?savedQueryId=${e.id}` : e.url}
-          title={getFilterTitle(e)}
-          description={`Last Edited: ${moment(
-            e.changed_on_utc,
-            'MM/DD/YYYY HH:mm:ss',
-          )}`}
-          avatar={getIconName(e)}
-          actions={null}
-        />
-      </CardStyles>
-    ));
-  };
+  const renderActivity = () =>
+    activityData[activeChild].map((entity: ActivityObject) => {
+      const url = getEntityUrl(entity);
+      const lastActionOn = getEntityLastActionOn(entity);
+      return (
+        <CardStyles
+          onClick={() => {
+            window.location.href = url;
+          }}
+          key={url}
+        >
+          <ListViewCard
+            loading={loading}
+            cover={<></>}
+            url={url}
+            title={getEntityTitle(entity)}
+            description={lastActionOn}
+            avatar={getEntityIconName(entity)}
+            actions={null}
+          />
+        </CardStyles>
+      );
+    });
+
   if (loading) return <Loading position="inline" />;
   return (
     <>
diff --git a/superset/views/core.py b/superset/views/core.py
index 8cc0b22..62b1b49 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -23,6 +23,7 @@
 from urllib import parse
 
 import backoff
+import humanize
 import pandas as pd
 import simplejson as json
 from flask import abort, flash, g, Markup, redirect, render_template, request, Response
@@ -1395,6 +1396,9 @@
                     "item_url": item_url,
                     "item_title": item_title,
                     "time": log.dttm,
+                    "time_delta_humanized": humanize.naturaltime(
+                        datetime.now() - log.dttm
+                    ),
                 }
             )
         return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))