diff --git a/.gitignore b/.gitignore
index 70e402a9dfdffcacc9f431c115d3071a325569af..5133c000b4eecd69872d722f3611950df039bec0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,3 +55,4 @@ bin/node_modules/
 *.log
 *.trace.db
 /backend-checksums.txt
+.vscode
diff --git a/frontend/src/metabase/components/CollectionLanding.jsx b/frontend/src/metabase/components/CollectionLanding.jsx
index 6b7e11cd7dded818c82f2ef6a5c885114258a891..c52fa787530cceefc570c0379e525314af4338d0 100644
--- a/frontend/src/metabase/components/CollectionLanding.jsx
+++ b/frontend/src/metabase/components/CollectionLanding.jsx
@@ -5,6 +5,9 @@ import { connect } from "react-redux";
 import { withRouter } from "react-router";
 import _ from "underscore";
 import cx from "classnames";
+import { dissoc } from "icepick";
+
+import withToast from "metabase/hoc/Toast";
 
 import listSelect from "metabase/hoc/ListSelect";
 import BulkActionBar from "metabase/components/BulkActionBar";
@@ -29,7 +32,9 @@ import CollectionEmptyState from "metabase/components/CollectionEmptyState";
 import Tooltip from "metabase/components/Tooltip";
 
 import CollectionMoveModal from "metabase/containers/CollectionMoveModal";
+import EntityCopyModal from "metabase/entities/containers/EntityCopyModal";
 import { entityObjectLoader } from "metabase/entities/containers/EntityObjectLoader";
+import { entityTypeForObject } from "metabase/schema";
 
 import CollectionList from "metabase/components/CollectionList";
 
@@ -122,7 +127,8 @@ import { entityListLoader } from "metabase/entities/containers/EntityListLoader"
 @withRouter
 class DefaultLanding extends React.Component {
   state = {
-    moveItems: null,
+    selectedItems: null,
+    selectedAction: null,
   };
 
   handleBulkArchive = async () => {
@@ -136,15 +142,18 @@ class DefaultLanding extends React.Component {
   };
 
   handleBulkMoveStart = () => {
-    this.setState({ moveItems: this.props.selected });
+    this.setState({
+      selectedItems: this.props.selected,
+      selectedAction: "move",
+    });
   };
 
   handleBulkMove = async collection => {
     try {
       await Promise.all(
-        this.state.moveItems.map(item => item.setCollection(collection)),
+        this.state.selectedItems.map(item => item.setCollection(collection)),
       );
-      this.setState({ moveItems: null });
+      this.setState({ selectedItems: null, selectedAction: null });
     } finally {
       this.handleBulkActionSuccess();
     }
@@ -174,7 +183,7 @@ class DefaultLanding extends React.Component {
       onToggleSelected,
       location,
     } = this.props;
-    const { moveItems } = this.state;
+    const { selectedItems, selectedAction } = this.state;
 
     const collectionWidth = unpinned.length > 0 ? [1, 1 / 3] : 1;
     const itemWidth = unpinned.length > 0 ? [1, 2 / 3] : 0;
@@ -288,7 +297,7 @@ class DefaultLanding extends React.Component {
                         >
                           <ItemDragSource item={item} collection={collection}>
                             <PinnedItem
-                              key={`${item.type}:${item.id}`}
+                              key={`${item.model}:${item.id}`}
                               index={index}
                               item={item}
                               collection={collection}
@@ -377,13 +386,22 @@ class DefaultLanding extends React.Component {
                                         collection={collection}
                                       >
                                         <NormalItem
-                                          key={`${item.type}:${item.id}`}
+                                          key={`${item.model}:${item.id}`}
                                           item={item}
                                           collection={collection}
                                           selection={selection}
                                           onToggleSelected={onToggleSelected}
-                                          onMove={moveItems =>
-                                            this.setState({ moveItems })
+                                          onMove={selectedItems =>
+                                            this.setState({
+                                              selectedItems,
+                                              selectedAction: "move",
+                                            })
+                                          }
+                                          onCopy={selectedItems =>
+                                            this.setState({
+                                              selectedItems,
+                                              selectedAction: "copy",
+                                            })
                                           }
                                         />
                                       </ItemDragSource>
@@ -479,18 +497,37 @@ class DefaultLanding extends React.Component {
             </BulkActionBar>
           </Box>
         </Box>
-        {!_.isEmpty(moveItems) && (
-          <Modal>
-            <CollectionMoveModal
-              title={
-                moveItems.length > 1
-                  ? t`Move ${moveItems.length} items?`
-                  : t`Move "${moveItems[0].getName()}"?`
-              }
-              onClose={() => this.setState({ moveItems: null })}
-              onMove={this.handleBulkMove}
-            />
-          </Modal>
+        {!_.isEmpty(selectedItems) &&
+          selectedAction == "copy" && (
+            <Modal>
+              <CollectionCopyEntityModal
+                entityObject={selectedItems[0]}
+                onClose={() =>
+                  this.setState({ selectedItems: null, selectedAction: null })
+                }
+                onSaved={newEntityObject => {
+                  this.setState({ selectedItems: null, selectedAction: null });
+                  this.handleBulkActionSuccess();
+                }}
+              />
+            </Modal>
+          )}
+        {!_.isEmpty(selectedItems) &&
+          selectedAction == "move" && (
+            <Modal>
+              <CollectionMoveModal
+                title={
+                  selectedItems.length > 1
+                    ? t`Move ${selectedItems.length} items?`
+                    : t`Move "${selectedItems[0].getName()}"?`
+                }
+                onClose={() =>
+                  this.setState({ selectedItems: null, selectedAction: null })
+                }
+                onMove={this.handleBulkMove}
+              />
+            </Modal>
+          )}
         )}
         <ItemsDragLayer selected={selected} />
       </Box>
@@ -504,6 +541,7 @@ export const NormalItem = ({
   selection = new Set(),
   onToggleSelected,
   onMove,
+  onCopy,
 }) => (
   <Link
     to={item.getUrl()}
@@ -515,7 +553,7 @@ export const NormalItem = ({
       showSelect={selection.size > 0}
       selectable
       item={item}
-      type={item.type}
+      type={entityTypeForObject(item)}
       name={item.getName()}
       iconName={item.getIcon()}
       iconColor={item.getColor()}
@@ -531,6 +569,7 @@ export const NormalItem = ({
       onMove={
         collection.can_write && item.setCollection ? () => onMove([item]) : null
       }
+      onCopy={item.copy ? () => onCopy([item]) : null}
       onArchive={
         collection.can_write && item.setArchived
           ? () => item.setArchived(true)
@@ -685,5 +724,38 @@ const CollectionBurgerMenu = () => (
     triggerIcon="burger"
   />
 );
+@withToast
+class CollectionCopyEntityModal extends React.Component {
+  render() {
+    const { entityObject, onClose, onSaved, triggerToast } = this.props;
+
+    return (
+      <EntityCopyModal
+        entityType={entityTypeForObject(entityObject)}
+        entityObject={entityObject}
+        copy={async values => {
+          return entityObject.copy(dissoc(values, "id"));
+        }}
+        onClose={onClose}
+        onSaved={newEntityObject => {
+          triggerToast(
+            <div className="flex align-center">
+              {t`Duplicated ${entityObject.model}`}
+              <Link
+                className="link text-bold ml1"
+                to={Urls.modelToUrl(entityObject.model, newEntityObject.id)}
+              >
+                {t`See it`}
+              </Link>
+            </div>,
+            { icon: entityObject.model },
+          );
+
+          onSaved(newEntityObject);
+        }}
+      />
+    );
+  }
+}
 
 export default CollectionLanding;
diff --git a/frontend/src/metabase/components/EntityItem.jsx b/frontend/src/metabase/components/EntityItem.jsx
index b17278d09ff181883d2797a4484096d93bacf9ce..a0864485846129ba07e710a511ebe9afa0d85208 100644
--- a/frontend/src/metabase/components/EntityItem.jsx
+++ b/frontend/src/metabase/components/EntityItem.jsx
@@ -30,6 +30,7 @@ const EntityItem = ({
   onPin,
   onFavorite,
   onMove,
+  onCopy,
   onArchive,
   selected,
   onToggleSelected,
@@ -50,8 +51,14 @@ const EntityItem = ({
       action: onMove,
       event: `${analyticsContext};Entity Item;Move Item;${item.model}`,
     },
+    onCopy && {
+      title: t`Duplicate this item`,
+      icon: "clone",
+      action: onCopy,
+      event: `${analyticsContext};Entity Item;Copy Item;${item.model}`,
+    },
     onArchive && {
-      title: t`Archive`,
+      title: t`Archive this item`,
       icon: "archive",
       action: onArchive,
       event: `${analyticsContext};Entity Item;Archive Item;${item.model}`,
diff --git a/frontend/src/metabase/components/form/StandardForm.jsx b/frontend/src/metabase/components/form/StandardForm.jsx
index d13f1cc01555ea5d4ff5528894970566bee933bd..5ac98c39c5f1bb89612041acf57ab8c8f898dc5f 100644
--- a/frontend/src/metabase/components/form/StandardForm.jsx
+++ b/frontend/src/metabase/components/form/StandardForm.jsx
@@ -20,6 +20,7 @@ const StandardForm = ({
   handleSubmit,
   resetForm,
 
+  submitTitle,
   formDef: form,
   className,
   resetButton = false,
@@ -64,7 +65,7 @@ const StandardForm = ({
           disabled={submitting || invalid}
           className="mr1"
         >
-          {values.id != null ? t`Update` : t`Create`}
+          {submitTitle || (values.id != null ? t`Update` : t`Create`)}
         </Button>
         {resetButton && (
           <Button
diff --git a/frontend/src/metabase/dashboard/components/DashboardCopyModal.jsx b/frontend/src/metabase/dashboard/components/DashboardCopyModal.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..ff7af4b8c9421077386d1bd698587ed56ea7f87d
--- /dev/null
+++ b/frontend/src/metabase/dashboard/components/DashboardCopyModal.jsx
@@ -0,0 +1,55 @@
+import React from "react";
+import { withRouter } from "react-router";
+import { connect } from "react-redux";
+import { dissoc } from "icepick";
+
+import { replace } from "react-router-redux";
+import * as Urls from "metabase/lib/urls";
+
+import Dashboards from "metabase/entities/dashboards";
+
+import EntityCopyModal from "metabase/entities/containers/EntityCopyModal";
+
+import { getDashboardComplete } from "../selectors";
+
+const mapStateToProps = (state, props) => {
+  return {
+    dashboard: getDashboardComplete(state, props),
+  };
+};
+
+const mapDispatchToProps = {
+  copyDashboard: Dashboards.actions.copy,
+  onReplaceLocation: replace,
+};
+
+@withRouter
+@connect(mapStateToProps, mapDispatchToProps)
+class DashboardCopyModal extends React.Component {
+  render() {
+    const {
+      onClose,
+      onReplaceLocation,
+      copyDashboard,
+      dashboard,
+      ...props
+    } = this.props;
+    return (
+      <EntityCopyModal
+        entityType="dashboards"
+        entityObject={dashboard}
+        copy={async values => {
+          return await copyDashboard(
+            { id: this.props.params.dashboardId },
+            dissoc(values, "id"),
+          );
+        }}
+        onClose={onClose}
+        onSaved={dashboard => onReplaceLocation(Urls.dashboard(dashboard.id))}
+        {...props}
+      />
+    );
+  }
+}
+
+export default DashboardCopyModal;
diff --git a/frontend/src/metabase/dashboard/components/DashboardHeader.jsx b/frontend/src/metabase/dashboard/components/DashboardHeader.jsx
index 3a3380e9e1bd8e1a4181fb9259e193d86e4264b1..aa6c4d4e1513a75063e4eb4b619545c80d81af0e 100644
--- a/frontend/src/metabase/dashboard/components/DashboardHeader.jsx
+++ b/frontend/src/metabase/dashboard/components/DashboardHeader.jsx
@@ -297,19 +297,6 @@ export default class DashboardHeader extends Component {
       );
     }
 
-    if (!isFullscreen) {
-      buttons.push(
-        <Tooltip key="new-dashboard" tooltip={t`Move dashboard`}>
-          <Link
-            to={location.pathname + "/move"}
-            data-metabase-event={"Dashboard;Move"}
-          >
-            <Icon className="text-brand-hover" name="move" size={18} />
-          </Link>
-        </Tooltip>,
-      );
-    }
-
     if (!isFullscreen && !isEditing && canEdit) {
       buttons.push(
         <Tooltip key="edit-dashboard" tooltip={t`Edit dashboard`}>
@@ -326,6 +313,29 @@ export default class DashboardHeader extends Component {
       );
     }
 
+    if (!isFullscreen && !isEditing) {
+      buttons.push(
+        <Tooltip key="new-dashboard" tooltip={t`Move dashboard`}>
+          <Link
+            to={location.pathname + "/move"}
+            data-metabase-event={"Dashboard;Move"}
+          >
+            <Icon className="text-brand-hover" name="move" size={18} />
+          </Link>
+        </Tooltip>,
+      );
+      buttons.push(
+        <Tooltip key="copy-dashboard" tooltip={t`Duplicate dashboard`}>
+          <Link
+            to={location.pathname + "/copy"}
+            data-metabase-event={"Dashboard;Copy"}
+          >
+            <Icon className="text-brand-hover" name="clone" size={18} />
+          </Link>
+        </Tooltip>,
+      );
+    }
+
     if (
       !isFullscreen &&
       ((isPublicLinksEnabled && (isAdmin || dashboard.public_uuid)) ||
diff --git a/frontend/src/metabase/entities/containers/EntityCopyModal.jsx b/frontend/src/metabase/entities/containers/EntityCopyModal.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..cb4944ef5025a9b517c76fa31495d6134fb340b4
--- /dev/null
+++ b/frontend/src/metabase/entities/containers/EntityCopyModal.jsx
@@ -0,0 +1,32 @@
+import React from "react";
+import { dissoc } from "icepick";
+import { t } from "c-3po";
+
+import EntityForm from "metabase/entities/containers/EntityForm";
+import ModalContent from "metabase/components/ModalContent";
+
+const EntityCopyModal = ({
+  entityType,
+  entityObject,
+  copy,
+  onClose,
+  onSaved,
+  ...props
+}) => (
+  <ModalContent title={t`Duplicate "${entityObject.name}"`} onClose={onClose}>
+    <EntityForm
+      entityType={entityType}
+      entityObject={{
+        ...dissoc(entityObject, "id"),
+        name: entityObject.name + " - " + t`Duplicate`,
+      }}
+      create={copy}
+      onClose={onClose}
+      onSaved={onSaved}
+      submitTitle={t`Duplicate`}
+      {...props}
+    />
+  </ModalContent>
+);
+
+export default EntityCopyModal;
diff --git a/frontend/src/metabase/entities/dashboards.js b/frontend/src/metabase/entities/dashboards.js
index 232872c4ebc64a175008f119dbdfd84dec3ea53c..1528b25b5ebb7fe51dd6a56a88d73721007c9498 100644
--- a/frontend/src/metabase/entities/dashboards.js
+++ b/frontend/src/metabase/entities/dashboards.js
@@ -1,11 +1,17 @@
 /* @flow */
 
+import { createThunkAction } from "metabase/lib/redux";
+import { setRequestState } from "metabase/redux/requests";
+import { normalize } from "normalizr";
+
 import { createEntity, undo } from "metabase/lib/entities";
 import * as Urls from "metabase/lib/urls";
 import { normal } from "metabase/lib/colors";
 import { assocIn } from "icepick";
 import { t } from "c-3po";
 
+import { addUndo } from "metabase/redux/undo";
+
 import { POST, DELETE } from "metabase/lib/api";
 import {
   canonicalCollectionId,
@@ -14,6 +20,7 @@ import {
 
 const FAVORITE_ACTION = `metabase/entities/dashboards/FAVORITE`;
 const UNFAVORITE_ACTION = `metabase/entities/dashboards/UNFAVORITE`;
+const COPY_ACTION = `metabase/entities/dashboards/COPY`;
 
 const Dashboards = createEntity({
   name: "dashboards",
@@ -23,6 +30,7 @@ const Dashboards = createEntity({
     favorite: POST("/api/dashboard/:id/favorite"),
     unfavorite: DELETE("/api/dashboard/:id/favorite"),
     save: POST("/api/dashboard/save"),
+    copy: POST("/api/dashboard/:id/copy"),
   },
 
   objectActions: {
@@ -59,6 +67,9 @@ const Dashboards = createEntity({
         return { type: UNFAVORITE_ACTION, payload: id };
       }
     },
+
+    copy: ({ id }, overrides, opts) =>
+      Dashboards.actions.copy({ id }, overrides, opts),
   },
 
   actions: {
@@ -70,6 +81,39 @@ const Dashboards = createEntity({
         payload: savedDashboard,
       };
     },
+
+    // TODO move into more common area as copy is implemented for more entities
+    copy: createThunkAction(
+      COPY_ACTION,
+      (entityObject, overrides, { notify } = {}) => async (
+        dispatch,
+        getState,
+      ) => {
+        const statePath = [
+          "entities",
+          entityObject.name,
+          entityObject.id,
+          "copy",
+        ];
+        try {
+          dispatch(setRequestState({ statePath, state: "LOADING" }));
+          const result = normalize(
+            await Dashboards.api.copy({ id: entityObject.id, ...overrides }),
+            Dashboards.schema,
+          );
+          dispatch(setRequestState({ statePath, state: "LOADED" }));
+          if (notify) {
+            dispatch(addUndo(notify));
+          }
+          dispatch({ type: Dashboards.actionTypes.INVALIDATE_LISTS_ACTION });
+          return result;
+        } catch (error) {
+          console.error(`${COPY_ACTION} failed:`, error);
+          dispatch(setRequestState({ statePath, error }));
+          throw error;
+        }
+      },
+    ),
   },
 
   reducer: (state = {}, { type, payload, error }) => {
@@ -77,6 +121,8 @@ const Dashboards = createEntity({
       return assocIn(state, [payload, "favorite"], true);
     } else if (type === UNFAVORITE_ACTION && !error) {
       return assocIn(state, [payload, "favorite"], false);
+    } else if (type === COPY_ACTION && !error && state[""]) {
+      return { ...state, "": state[""].concat([payload.result]) };
     }
     return state;
   },
diff --git a/frontend/src/metabase/icon_paths.js b/frontend/src/metabase/icon_paths.js
index 38ef292c3e480838007bd5f25540fc3e05ef34c6..43f250ca01b40ab88fe19dcd37b8c1704010fdfa 100644
--- a/frontend/src/metabase/icon_paths.js
+++ b/frontend/src/metabase/icon_paths.js
@@ -75,8 +75,8 @@ export const ICON_PATHS = {
     "M16 0 A16 16 0 0 0 0 16 A16 16 0 0 0 16 32 A16 16 0 0 0 32 16 A16 16 0 0 0 16 0 M16 4 A12 12 0 0 1 28 16 A12 12 0 0 1 16 28 A12 12 0 0 1 4 16 A12 12 0 0 1 16 4 M14 6 L14 17.25 L22 22 L24.25 18.5 L18 14.75 L18 6z",
   clone: {
     path:
-      "M12,11 L16,11 L16,0 L5,0 L5,3 L12,3 L12,11 L12,11 Z M0,4 L11,4 L11,15 L0,15 L0,4 Z",
-    attrs: { viewBox: "0 0 16 15" },
+      "M24,22 L32,22 L32,0 L10,0 L10,6 L24,6 L24,22 L24,22 Z M0,8 L22,8 L22,30 L0,30 L0,8 Z",
+    attrs: { viewBox: "0 0 32 30" },
   },
   close:
     "M4 8 L8 4 L16 12 L24 4 L28 8 L20 16 L28 24 L24 28 L16 20 L8 28 L4 24 L12 16 z ",
diff --git a/frontend/src/metabase/routes.jsx b/frontend/src/metabase/routes.jsx
index bec70959aba10109c797f1128c7736b47ff60c86..943d0c0db17824831c8eb3b6464e4913e5485bcd 100644
--- a/frontend/src/metabase/routes.jsx
+++ b/frontend/src/metabase/routes.jsx
@@ -87,6 +87,7 @@ import PublicQuestion from "metabase/public/containers/PublicQuestion.jsx";
 import PublicDashboard from "metabase/public/containers/PublicDashboard.jsx";
 import { DashboardHistoryModal } from "metabase/dashboard/components/DashboardHistoryModal";
 import DashboardMoveModal from "metabase/dashboard/components/DashboardMoveModal";
+import DashboardCopyModal from "metabase/dashboard/components/DashboardCopyModal";
 import { ModalRoute } from "metabase/hoc/ModalRoute";
 
 import CollectionLanding from "metabase/components/CollectionLanding";
@@ -219,6 +220,7 @@ export const getRoutes = store => (
         >
           <ModalRoute path="history" modal={DashboardHistoryModal} />
           <ModalRoute path="move" modal={DashboardMoveModal} />
+          <ModalRoute path="copy" modal={DashboardCopyModal} />
         </Route>
 
         <Route path="/question">
diff --git a/src/metabase/api/dashboard.clj b/src/metabase/api/dashboard.clj
index 47e6f822194f9a2d08ac22b041f238cf07bf49e9..4cbf7a920fcc03ca9133d81b29ea2d5bb57ae8af 100644
--- a/src/metabase/api/dashboard.clj
+++ b/src/metabase/api/dashboard.clj
@@ -188,6 +188,45 @@
   (update dashboard :ordered_cards add-query-average-duration-to-dashcards))
 
 
+(defn- get-dashboard
+  "Get `Dashboard` with ID."
+  [id]
+  (-> (Dashboard id)
+      api/check-404
+      (hydrate [:ordered_cards :card :series] :can_write)
+      api/read-check
+      api/check-not-archived
+      hide-unreadable-cards
+      add-query-average-durations))
+
+
+(api/defendpoint POST "/:from-dashboard-id/copy"
+  "Copy a `Dashboard`."
+  [from-dashboard-id :as {{:keys [name description collection_id collection_position], :as dashboard} :body}]
+  {name                (s/maybe su/NonBlankString)
+   description         (s/maybe s/Str)
+   collection_id       (s/maybe su/IntGreaterThanZero)
+   collection_position (s/maybe su/IntGreaterThanZero)}
+  ;; if we're trying to save the new dashboard in a Collection make sure we have permissions to do that
+  (collection/check-write-perms-for-collection collection_id)
+  (let [existing-dashboard (get-dashboard from-dashboard-id)
+        dashboard-data {:name                (or name (:name existing-dashboard))
+                        :description         (or description (:description existing-dashboard))
+                        :parameters          (or (:parameters existing-dashboard) [])
+                        :creator_id          api/*current-user-id*
+                        :collection_id       collection_id
+                        :collection_position collection_position}
+        dashboard      (db/transaction
+                            ;; Adding a new dashboard at `collection_position` could cause other dashboards in this collection to change
+                            ;; position, check that and fix up if needed
+                            (api/maybe-reconcile-collection-position! dashboard-data)
+                            ;; Ok, now save the Dashboard
+                            (u/prog1 (db/insert! Dashboard dashboard-data)
+                                    ;; Get cards from existing dashboard and associate to copied dashboard
+                                    (doseq [card (:ordered_cards existing-dashboard)]
+                                      (api/check-500 (dashboard/add-dashcard! <> (:card_id card) card)))))]
+    (events/publish-event! :dashboard-create dashboard)))
+
 
 ;;; --------------------------------------------- Fetching/Updating/Etc. ---------------------------------------------
 
diff --git a/test/metabase/api/dashboard_test.clj b/test/metabase/api/dashboard_test.clj
index a2626777817ec1bd90f45f5a1a3f915079ef10ec..df062100c1a897eed0f1896e9e3cdf0e1681eba4 100644
--- a/test/metabase/api/dashboard_test.clj
+++ b/test/metabase/api/dashboard_test.clj
@@ -543,6 +543,60 @@
        (Dashboard dashboard-id)])))
 
 
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                         COPY /api/dashboard/:id/copy                                           |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; A plain copy with nothing special
+(expect (merge dashboard-defaults
+               {:name               "Test Dashboard"
+                :description        "A description"
+                :creator_id         (user->id :rasta)
+                :collection_id      false})
+        (tt/with-temp Dashboard  [{dashboard-id :id} {:name         "Test Dashboard"
+                                                      :description  "A description"
+                                                      :creator_id   (user->id :rasta)}]
+          (tu/with-model-cleanup [Dashboard]
+            (dashboard-response ((user->client :rasta) :post 200 (format "dashboard/%d/copy" dashboard-id))))))
+
+;; Ensure name / description / user set when copying
+(expect (merge dashboard-defaults
+               {:name           "Test Dashboard - Duplicate"
+                :description    "A new description"
+                :creator_id     (user->id :crowberto)
+                :collection_id  false})
+        (tt/with-temp Dashboard [{dashboard-id :id}  {:name           "Test Dashboard"
+                                                      :description    "An old description"}]
+          (tu/with-model-cleanup [Dashboard]
+            (dashboard-response ((user->client :crowberto) :post 200 (format "dashboard/%d/copy" dashboard-id)
+              {:name             "Test Dashboard - Duplicate"
+               :description      "A new description"})))))
+
+;; Ensure dashboard cards are copied
+(expect
+  2
+  (tt/with-temp* [Dashboard     [{dashboard-id :id}  {:name "Test Dashboard"}]
+                  Card          [{card-id :id}]
+                  Card          [{card-id2 :id}]
+                  DashboardCard [{dashcard-id :id} {:dashboard_id dashboard-id, :card_id card-id}]
+                  DashboardCard [{dashcard-id :id} {:dashboard_id dashboard-id, :card_id card-id2}]]
+    (tu/with-model-cleanup [Dashboard]
+      (count (db/select-ids DashboardCard, :dashboard_id
+        (u/get-id ((user->client :rasta) :post 200 (format "dashboard/%d/copy" dashboard-id))))))))
+
+;; Ensure the correct collection is set when copying
+(expect
+  (tu/with-model-cleanup [Dashboard]
+    (dashboard-test/with-dash-in-collection [db collection dash]
+      (tt/with-temp Collection [new-collection]
+        ;; grant Permissions for both new and old collections
+        (doseq [coll [collection new-collection]]
+          (perms/grant-collection-readwrite-permissions! (group/all-users) coll))
+        ;; Check to make sure the ID of the collection is correct
+        (= (db/select-one-field :collection_id Dashboard :id
+              (u/get-id ((user->client :rasta) :post 200 (format "dashboard/%d/copy" (u/get-id dash)) {:collection_id (u/get-id new-collection)})))
+          (u/get-id new-collection))))))
+
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                         POST /api/dashboard/:id/cards                                          |
 ;;; +----------------------------------------------------------------------------------------------------------------+